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..77cea7e38d 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 @@ -19,8 +19,7 @@ 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 io.circe.{Codec, Decoder, Encoder, Json} import org.scalasteward.core.repoconfig.PullRequestGroup import org.scalasteward.core.util import org.scalasteward.core.util.Nel @@ -198,66 +197,87 @@ 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 ForArtifactIdEncoder: Encoder[ForArtifactId] = { - val derived = deriveEncoder[ForArtifactId] - - derived.mapJson(json => Json.obj("ForArtifactId" -> json)) - } - - implicit val ForArtifactIdDecoder: Decoder[ForArtifactId] = { - val derived = deriveDecoder[ForArtifactId] - derived - .prepare(_.downField("ForArtifactId")) - .or(derived.prepare(_.downField("Single"))) - } + implicit val SingleOrder: Order[Single] = + Order.by((u: Single) => (u.crossDependencies, u.newerVersions)) - implicit val ForGroupIdEncoder: Encoder[ForGroupId] = { - val derived = deriveEncoder[ForGroupId] + implicit val forArtifactIdEncoder: Encoder[ForArtifactId] = + deriveEncoder[ForArtifactId].mapJson(json => Json.obj("ForArtifactId" -> json)) - derived.mapJson(json => Json.obj("ForGroupId" -> json)) - } + implicit val forArtifactIdDecoder: Decoder[ForArtifactId] = + deriveDecoder[ForArtifactId].prepare(_.downField("ForArtifactId")) - private val oldForGroupIdDecoder: Decoder[ForGroupId] = - (c: HCursor) => - for { - crossDependencies <- c.downField("crossDependencies").as[Nel[CrossDependency]] - newerVersions <- c.downField("newerVersions").as[Nel[Version]] - forArtifactIds = crossDependencies - .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"))) - - implicit val GroupedEncoder: Encoder[Grouped] = { - val derived = deriveEncoder[Grouped] - - derived.mapJson(json => Json.obj("Grouped" -> json)) + implicit val updateEncoder: Encoder[Update] = { + case update: ForArtifactId => forArtifactIdEncoder.apply(update) + case update => deriveEncoder[Update].apply(update) } - implicit val GroupedDecoder: Decoder[Grouped] = - deriveDecoder[Grouped].prepare(_.downField("Grouped")) - - implicit val SingleOrder: Order[Single] = - Order.by((u: Single) => (u.crossDependencies, u.newerVersions)) + implicit val updateDecoder: Decoder[Update] = + deriveDecoder[Update] + .or(deriveDecoder[compat.v2.Update].map(_.toV3)) + .or(deriveDecoder[compat.v1.Update].map(_.toV3)) + + object compat { + // the current version + import org.scalasteward.core.data as v3 + + // before https://github.com/scala-steward-org/scala-steward/pull/2898 + object v2 { + sealed trait Update { + def toV3: v3.Update = + this match { + case forArtifactId: ForArtifactId => + forArtifactId.toV3 + case ForGroupId(crossDependencies, newerVersions) => + v3.Update.ForGroupId(crossDependencies.map(v3.Update.ForArtifactId(_, newerVersions))) + case Grouped(name, title, updates) => + v3.Update.Grouped(name, title, updates.map(_.toV3)) + } + } + sealed trait Single extends Update + final case class ForArtifactId( + crossDependency: CrossDependency, + newerVersions: Nel[Version], + newerGroupId: Option[GroupId], + newerArtifactId: Option[String] + ) extends Single { + override def toV3: v3.Update.ForArtifactId = + v3.Update.ForArtifactId(crossDependency, newerVersions, newerGroupId, newerArtifactId) + } + implicit val forArtifactIdDecoder: Decoder[ForArtifactId] = + deriveDecoder[ForArtifactId] // .prepare(_.downField("ForArtifactId")) + final case class ForGroupId( + crossDependencies: Nel[CrossDependency], + newerVersions: Nel[Version] + ) extends Single + final case class Grouped( + name: String, + title: Option[String], + updates: List[ForArtifactId] + ) extends Update + } - implicit val UpdateEncoder: Encoder[Update] = { - case update: Grouped => update.asJson - case update: ForArtifactId => update.asJson - case update: ForGroupId => update.asJson + // before https://github.com/scala-steward-org/scala-steward/pull/2714 + object v1 { + sealed trait Update { + def toV3: v3.Update = toV2.toV3 + private def toV2: v2.Update = + this match { + case Single(crossDependency, newerVersions, newerGroupId, newerArtifactId) => + v2.ForArtifactId(crossDependency, newerVersions, newerGroupId, newerArtifactId) + case Group(crossDependencies, newerVersions) => + v2.ForGroupId(crossDependencies, newerVersions) + } + } + final case class Single( + crossDependency: CrossDependency, + newerVersions: Nel[Version], + newerGroupId: Option[GroupId], + newerArtifactId: Option[String] + ) extends Update + final case class Group( + crossDependencies: Nel[CrossDependency], + newerVersions: Nel[Version] + ) extends Update + } } - - implicit val UpdateDecoder: Decoder[Update] = - ForArtifactIdDecoder - .widen[Update] - .or(ForGroupIdDecoder.widen[Update]) - .or(Decoder[Grouped].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..2c028be310 --- /dev/null +++ b/modules/core/src/test/scala/org/scalasteward/core/data/UpdateCodecTest.scala @@ -0,0 +1,261 @@ +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 + println(Encoder[Update].apply(update)) + 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]) - } }