diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala b/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala index 150695d5a8..99d37e7189 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala @@ -18,7 +18,6 @@ package org.scalasteward.core.data import cats.Order import cats.implicits.* -import io.circe.generic.semiauto.* import io.circe.syntax.* import io.circe.{Decoder, Encoder, HCursor, Json} import org.scalasteward.core.repoconfig.PullRequestGroup @@ -198,30 +197,57 @@ object Update { } } - // TODO: Derive all instances of `Encoder`/`Decoder` here using `deriveCodec` - // Partially manually implemented so we don't fail reading old caches (those - // still using `Single`/`Group`) + implicit val SingleOrder: Order[Single] = + Order.by((u: Single) => (u.crossDependencies, u.newerVersions)) - implicit val ForArtifactIdEncoder: Encoder[ForArtifactId] = { - val derived = deriveEncoder[ForArtifactId] + // Encoder and Decoder instances - derived.mapJson(json => Json.obj("ForArtifactId" -> json)) - } + // ForArtifactId - implicit val ForArtifactIdDecoder: Decoder[ForArtifactId] = { - val derived = deriveDecoder[ForArtifactId] - derived - .prepare(_.downField("ForArtifactId")) - .or(derived.prepare(_.downField("Single"))) - } - - implicit val ForGroupIdEncoder: Encoder[ForGroupId] = { - val derived = deriveEncoder[ForGroupId] + private val forArtifactIdEncoder: Encoder[ForArtifactId] = + Encoder.instance[ForArtifactId] { forArtifactId => + Json.obj( + "ForArtifactId" -> Json.obj( + "crossDependency" -> forArtifactId.crossDependency.asJson, + "newerVersions" -> forArtifactId.newerVersions.asJson, + "newerGroupId" -> forArtifactId.newerGroupId.asJson, + "newerArtifactId" -> forArtifactId.newerArtifactId.asJson + ) + ) + } - derived.mapJson(json => Json.obj("ForGroupId" -> json)) - } + private val unwrappedForArtifactIdDecoder: Decoder[ForArtifactId] = + (c: HCursor) => + for { + crossDependency <- c.downField("crossDependency").as[CrossDependency] + newerVersions <- c.downField("newerVersions").as[Nel[Version]] + newerGroupId <- c.downField("newerGroupId").as[Option[GroupId]] + newerArtifactId <- c.downField("newerArtifactId").as[Option[String]] + } yield ForArtifactId(crossDependency, newerVersions, newerGroupId, newerArtifactId) + + private val forArtifactIdDecoderV1: Decoder[ForArtifactId] = + unwrappedForArtifactIdDecoder.prepare(_.downField("Single")) + + private val forArtifactIdDecoderV2 = + unwrappedForArtifactIdDecoder.prepare(_.downField("ForArtifactId")) + + private val forArtifactIdDecoder: Decoder[ForArtifactId] = + forArtifactIdDecoderV2.or(forArtifactIdDecoderV1) + + // ForGroupId + + private val forGroupIdEncoder: Encoder[ForGroupId] = + Encoder.instance[ForGroupId] { forGroupId => + Json.obj( + "ForGroupId" -> Json.obj( + "forArtifactIds" -> Encoder + .encodeNonEmptyList(forArtifactIdEncoder) + .apply(forGroupId.forArtifactIds) + ) + ) + } - private val oldForGroupIdDecoder: Decoder[ForGroupId] = + private val unwrappedForGroupIdDecoderV1: Decoder[ForGroupId] = (c: HCursor) => for { crossDependencies <- c.downField("crossDependencies").as[Nel[CrossDependency]] @@ -230,34 +256,60 @@ object Update { .map(crossDependency => ForArtifactId(crossDependency, newerVersions)) } yield ForGroupId(forArtifactIds) - implicit val ForGroupIdDecoder: Decoder[ForGroupId] = - deriveDecoder[ForGroupId] - .prepare(_.downField("ForGroupId")) - .or(oldForGroupIdDecoder.prepare(_.downField("ForGroupId"))) - .or(oldForGroupIdDecoder.prepare(_.downField("Group"))) + private val unwrappedForGroupIdDecoderV3: Decoder[ForGroupId] = + (c: HCursor) => + for { + forArtifactIds <- Decoder + .decodeNonEmptyList(forArtifactIdDecoder) + .tryDecode(c.downField("forArtifactIds")) + } yield ForGroupId(forArtifactIds) - implicit val GroupedEncoder: Encoder[Grouped] = { - val derived = deriveEncoder[Grouped] + private val forGroupIdDecoderV1: Decoder[ForGroupId] = + unwrappedForGroupIdDecoderV1.prepare(_.downField("Group")) - derived.mapJson(json => Json.obj("Grouped" -> json)) - } + private val forGroupIdDecoderV2: Decoder[ForGroupId] = + unwrappedForGroupIdDecoderV1.prepare(_.downField("ForGroupId")) - implicit val GroupedDecoder: Decoder[Grouped] = - deriveDecoder[Grouped].prepare(_.downField("Grouped")) + private val forGroupIdDecoderV3: Decoder[ForGroupId] = + unwrappedForGroupIdDecoderV3.prepare(_.downField("ForGroupId")) - implicit val SingleOrder: Order[Single] = - Order.by((u: Single) => (u.crossDependencies, u.newerVersions)) + private val forGroupIdDecoder: Decoder[ForGroupId] = + forGroupIdDecoderV3.or(forGroupIdDecoderV2).or(forGroupIdDecoderV1) + + // Grouped + + private val groupedEncoder: Encoder[Grouped] = + Encoder.instance[Grouped] { grouped => + Json.obj( + "Grouped" -> Json.obj( + "name" -> grouped.name.asJson, + "title" -> grouped.title.asJson, + "updates" -> Encoder.encodeList(forArtifactIdEncoder).apply(grouped.updates) + ) + ) + } + + private val groupedDecoder: Decoder[Grouped] = + (c: HCursor) => { + val c1 = c.downField("Grouped") + for { + name <- c1.downField("name").as[String] + title <- c1.downField("title").as[Option[String]] + updates <- Decoder.decodeList(forArtifactIdDecoder).tryDecode(c1.downField("updates")) + } yield Grouped(name, title, updates) + } + + // Update - implicit val UpdateEncoder: Encoder[Update] = { - case update: Grouped => update.asJson - case update: ForArtifactId => update.asJson - case update: ForGroupId => update.asJson + implicit val updateEncoder: Encoder[Update] = { + case update: ForArtifactId => forArtifactIdEncoder.apply(update) + case update: ForGroupId => forGroupIdEncoder.apply(update) + case update: Grouped => groupedEncoder.apply(update) } - implicit val UpdateDecoder: Decoder[Update] = - ForArtifactIdDecoder + implicit val updateDecoder: Decoder[Update] = + forArtifactIdDecoder .widen[Update] - .or(ForGroupIdDecoder.widen[Update]) - .or(Decoder[Grouped].widen[Update]) - + .or(forGroupIdDecoder.widen[Update]) + .or(groupedDecoder.widen[Update]) } diff --git a/modules/core/src/test/scala/org/scalasteward/core/data/UpdateCodecTest.scala b/modules/core/src/test/scala/org/scalasteward/core/data/UpdateCodecTest.scala new file mode 100644 index 0000000000..c1dc56e7a8 --- /dev/null +++ b/modules/core/src/test/scala/org/scalasteward/core/data/UpdateCodecTest.scala @@ -0,0 +1,260 @@ +package org.scalasteward.core.data + +import io.circe.syntax.* +import io.circe.{Decoder, Encoder, Json} +import munit.FunSuite +import org.scalasteward.core.TestSyntax.* +import org.scalasteward.core.util.Nel + +class UpdateCodecTest extends FunSuite { + test("Decoder[ForArtifactId] V1") { + val json = Json.obj( + "Single" := Json.obj( + "crossDependency" := Json.arr( + Json.obj( + "groupId" := "org.specs2", + "artifactId" := Json.obj( + "name" := "specs2-core", + "maybeCrossName" := None + ), + "version" := "3.9.4", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := "test" + ) + ), + "newerVersions" := List("3.9.5"), + "newerGroupId" := None, + "newerArtifactId" := None + ) + ) + val obtained = Decoder[Update].decodeJson(json) + val expected = Right(("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single) + assertEquals(obtained, expected) + } + + test("Decoder[ForArtifactId] V2") { + val json = Json.obj( + "ForArtifactId" := Json.obj( + "crossDependency" := Json.arr( + Json.obj( + "groupId" := "org.specs2", + "artifactId" := Json.obj( + "name" := "specs2-core", + "maybeCrossName" := None + ), + "version" := "3.9.4", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := "test" + ) + ), + "newerVersions" := List("3.9.5"), + "newerGroupId" := None, + "newerArtifactId" := None + ) + ) + val obtained = Decoder[Update].decodeJson(json) + val expected = Right(("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single) + assertEquals(obtained, expected) + } + + test("Codec[ForArtifactId]") { + val update = ("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single + val obtained = Decoder[Update].decodeJson(Encoder[Update].apply(update)) + assertEquals(obtained, Right(update)) + } + + test("Decoder[ForGroupId] V1") { + val json = Json.obj( + "Group" := Json.obj( + "crossDependencies" := Json.arr( + Json.arr( + Json.obj( + "groupId" := "org.scala-sbt", + "artifactId" := Json.obj( + "name" := "sbt-launch", + "maybeCrossName" := None + ), + "version" := "1.2.1", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := None + ) + ), + Json.arr( + Json.obj( + "groupId" := "org.scala-sbt", + "artifactId" := Json.obj( + "name" := "scripted-plugin", + "maybeCrossName" := None + ), + "version" := "1.2.1", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := None + ) + ) + ), + "newerVersions" := List("1.2.4") + ) + ) + val obtained = Decoder[Update].decodeJson(json) + val expected = Right( + ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group + ) + assertEquals(obtained, expected) + } + + test("Decoder[ForGroupId] V2") { + val json = Json.obj( + "ForGroupId" := Json.obj( + "crossDependencies" := Json.arr( + Json.arr( + Json.obj( + "groupId" := "org.scala-sbt", + "artifactId" := Json.obj( + "name" := "sbt-launch", + "maybeCrossName" := None + ), + "version" := "1.2.1", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := None + ) + ), + Json.arr( + Json.obj( + "groupId" := "org.scala-sbt", + "artifactId" := Json.obj( + "name" := "scripted-plugin", + "maybeCrossName" := None + ), + "version" := "1.2.1", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := None + ) + ) + ), + "newerVersions" := List("1.2.4") + ) + ) + val obtained = Decoder[Update].decodeJson(json) + val expected = Right( + ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group + ) + assertEquals(obtained, expected) + } + + test("Decoder[ForGroupId] V3") { + val json = Json.obj( + "ForGroupId" := Json.obj( + "forArtifactIds" := Json.arr( + Json.obj( + "ForArtifactId" := Json.obj( + "crossDependency" := Json.arr( + Json.obj( + "groupId" := "org.scala-sbt", + "artifactId" := Json.obj( + "name" := "sbt-launch", + "maybeCrossName" := None + ), + "version" := "1.2.1", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := None + ) + ), + "newerVersions" := List("1.2.4"), + "newerGroupId" := None, + "newerArtifactId" := None + ) + ), + Json.obj( + "ForArtifactId" := Json.obj( + "crossDependency" := Json.arr( + Json.obj( + "groupId" := "org.scala-sbt", + "artifactId" := Json.obj( + "name" := "scripted-plugin", + "maybeCrossName" := None + ), + "version" := "1.2.1", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := None + ) + ), + "newerVersions" := List("1.2.4"), + "newerGroupId" := None, + "newerArtifactId" := None + ) + ) + ) + ) + ) + val obtained = Decoder[Update].decodeJson(json) + val expected = Right( + ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group + ) + assertEquals(obtained, expected) + } + + test("Codec[ForGroupId]") { + val update = + ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group + val obtained = Decoder[Update].decodeJson(Encoder[Update].apply(update)) + assertEquals(obtained, Right(update)) + } + + test("Decoder[Grouped]") { + val json = Json.obj( + "Grouped" := Json.obj( + "name" := "all", + "title" := "All", + "updates" := Json.arr( + Json.obj( + "ForArtifactId" := Json.obj( + "crossDependency" := Json.arr( + Json.obj( + "groupId" := "org.specs2", + "artifactId" := Json.obj( + "name" := "specs2-core", + "maybeCrossName" := None + ), + "version" := "3.9.4", + "sbtVersion" := None, + "scalaVersion" := None, + "configurations" := "test" + ) + ), + "newerVersions" := List("3.9.5"), + "newerGroupId" := None, + "newerArtifactId" := None + ) + ) + ) + ) + ) + val obtained = Decoder[Update].decodeJson(json) + val expected = Right( + Update.Grouped( + name = "all", + title = Some("All"), + updates = List(("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single) + ) + ) + assertEquals(obtained, expected) + } + + test("Codec[Grouped]") { + val update = Update.Grouped( + name = "all", + title = Some("All"), + updates = List(("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single) + ) + val obtained = Decoder[Update].decodeJson(Encoder[Update].apply(update)) + assertEquals(obtained, Right(update)) + } +} diff --git a/modules/core/src/test/scala/org/scalasteward/core/data/UpdateTest.scala b/modules/core/src/test/scala/org/scalasteward/core/data/UpdateTest.scala index 4259fd33c9..2db54a8f54 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/data/UpdateTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/data/UpdateTest.scala @@ -1,7 +1,5 @@ package org.scalasteward.core.data -import io.circe.Json -import io.circe.syntax.* import munit.FunSuite import org.scalasteward.core.TestSyntax.* import org.scalasteward.core.util.Nel @@ -69,184 +67,4 @@ class UpdateTest extends FunSuite { ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group assertEquals(update.show, "org.scala-sbt:{sbt-launch, scripted-plugin} : 1.2.1 -> 1.2.4") } - - test("ForArtifactId Encoder/Decoder") { - val update1: Update = - ("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single - val update2: Update.ForArtifactId = - ("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single - - val expected = Json.obj( - "ForArtifactId" := Json.obj( - "crossDependency" := Json.arr( - Json.obj( - "groupId" := "org.specs2", - "artifactId" := Json.obj( - "name" := "specs2-core", - "maybeCrossName" := None - ), - "version" := "3.9.4", - "sbtVersion" := None, - "scalaVersion" := None, - "configurations" := "test" - ) - ), - "newerVersions" := List("3.9.5"), - "newerGroupId" := None, - "newerArtifactId" := None - ) - ) - - assertEquals(update1.asJson, expected) - assertEquals(update1.asJson, update2.asJson) - assertEquals(update1.asJson.as[Update], update2.asJson.as[Update]) - assertEquals(update1.asJson.as[Update.ForArtifactId], update2.asJson.as[Update.ForArtifactId]) - assertEquals(update1.asJson.as[Update], update2.asJson.as[Update.ForArtifactId]) - } - - test("ForGroupId Encoder/Decoder") { - val update1: Update = - ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group - val update2: Update.ForGroupId = - ("org.scala-sbt".g % Nel.of("sbt-launch".a, "scripted-plugin".a) % "1.2.1" %> "1.2.4").group - - val oldJson = Json.obj( - "ForGroupId" := Json.obj( - "crossDependencies" := Json.arr( - Json.arr( - Json.obj( - "groupId" := "org.scala-sbt", - "artifactId" := Json.obj( - "name" := "sbt-launch", - "maybeCrossName" := None - ), - "version" := "1.2.1", - "sbtVersion" := None, - "scalaVersion" := None, - "configurations" := None - ) - ), - Json.arr( - Json.obj( - "groupId" := "org.scala-sbt", - "artifactId" := Json.obj( - "name" := "scripted-plugin", - "maybeCrossName" := None - ), - "version" := "1.2.1", - "sbtVersion" := None, - "scalaVersion" := None, - "configurations" := None - ) - ) - ), - "newerVersions" := List("1.2.4") - ) - ) - - val newJson = Json.obj( - "ForGroupId" := Json.obj( - "forArtifactIds" := Json.arr( - Json.obj( - "ForArtifactId" := Json.obj( - "crossDependency" := Json.arr( - Json.obj( - "groupId" := "org.scala-sbt", - "artifactId" := Json.obj( - "name" := "sbt-launch", - "maybeCrossName" := None - ), - "version" := "1.2.1", - "sbtVersion" := None, - "scalaVersion" := None, - "configurations" := None - ) - ), - "newerVersions" := List("1.2.4"), - "newerGroupId" := None, - "newerArtifactId" := None - ) - ), - Json.obj( - "ForArtifactId" := Json.obj( - "crossDependency" := Json.arr( - Json.obj( - "groupId" := "org.scala-sbt", - "artifactId" := Json.obj( - "name" := "scripted-plugin", - "maybeCrossName" := None - ), - "version" := "1.2.1", - "sbtVersion" := None, - "scalaVersion" := None, - "configurations" := None - ) - ), - "newerVersions" := List("1.2.4"), - "newerGroupId" := None, - "newerArtifactId" := None - ) - ) - ) - ) - ) - - assertEquals(oldJson.as[Update.ForGroupId], Right(update2)) - assertEquals(newJson.as[Update.ForGroupId], Right(update2)) - assertEquals(update1.asJson, newJson) - assertEquals(update1.asJson, update2.asJson) - assertEquals(update1.asJson.as[Update], update2.asJson.as[Update]) - assertEquals(update1.asJson.as[Update.ForGroupId], update2.asJson.as[Update.ForGroupId]) - assertEquals(update1.asJson.as[Update], update2.asJson.as[Update.ForGroupId]) - } - - test("Grouped Encoder/Decoder") { - val update1: Update = - Update.Grouped( - name = "all", - title = Some("All"), - updates = List(("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single) - ) - val update2: Update.Grouped = - Update.Grouped( - name = "all", - title = Some("All"), - updates = List(("org.specs2".g % "specs2-core".a % "3.9.4" % "test" %> "3.9.5").single) - ) - - val expected = Json.obj( - "Grouped" := Json.obj( - "name" := "all", - "title" := "All", - "updates" := Json.arr( - Json.obj( - "ForArtifactId" := Json.obj( - "crossDependency" := Json.arr( - Json.obj( - "groupId" := "org.specs2", - "artifactId" := Json.obj( - "name" := "specs2-core", - "maybeCrossName" := None - ), - "version" := "3.9.4", - "sbtVersion" := None, - "scalaVersion" := None, - "configurations" := "test" - ) - ), - "newerVersions" := List("3.9.5"), - "newerGroupId" := None, - "newerArtifactId" := None - ) - ) - ) - ) - ) - - assertEquals(update1.asJson, expected) - assertEquals(update1.asJson, update2.asJson) - assertEquals(update1.asJson.as[Update], update2.asJson.as[Update]) - assertEquals(update1.asJson.as[Update.Grouped], update2.asJson.as[Update.Grouped]) - assertEquals(update1.asJson.as[Update], update2.asJson.as[Update.Grouped]) - } }