diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index e75e6a3..bf5a6c5 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - scala: [2.12.19, 2.13.14, 3.3.0] + scala: [2.12.20, 2.13.15, 3.3.4] steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 71d9e97..abd995c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,13 @@ It's advantages: * Configurable recursive semiauto derivation * Discriminator support for sum types derivation - +Read more about library usage bellow: +- [Scala 3](https://github.com/tethys-json/tethys?tab=readme-ov-file#scala-3) +- [Scala 2](https://github.com/tethys-json/tethys?tab=readme-ov-file#scala-2) + + +# Scala 3 + ## Quick start ```scala @@ -210,9 +216,9 @@ json.jsonAs[Session] == Right(session) session.asJson == json ``` -## Sealed traits and enums +### Sealed traits and enums To derive **JsonReader** you **must** provide a discriminator. -This can be done via **selector** annotation +This can be done via **selector** annotation. Discriminator for **JsonWriter** is optional. If you don't need readers/writers for subtypes, you can omit them, @@ -383,15 +389,14 @@ case object Direction extends Enum[Direction] ``` -## scala 2 +### Migration notes -### migration notes -When migrating to **scala 3** you should use **0.28.1** version. +When migrating to **scala 3** you should use **0.29.0** version. -Scala 3 derivation API in **1.0.0** has a lot of deprecations and is not fully compatible with **0.28.1**, including: +Scala 3 derivation API in **0.29.0** has a lot of deprecations and is not fully compatible in compile time with **0.28.4**, including: 1. **WriterDescription** and **ReaderDescription** are deprecated along with **describe** macro. - You can use **WriterBuilder** and **ReaderBuilder** directly instead + Use **WriterBuilder** and **ReaderBuilder** directly instead 2. **DependentField** model for **ReaderBuilder** has changed. @@ -407,20 +412,21 @@ Scala 3 derivation API in **1.0.0** has a lot of deprecations and is not fully c .extract(_.i).from(_.d).and[Double]("e")((d, e) => (d + e).toInt) ``` -3. **0.28.1 scala 3 enum support** will not compile to prevent runtime effects during migration +3. **0.28.4 scala 3 enum support** was changed. [See more](https://github.com/tethys-json/tethys?tab=readme-ov-file#basic-enums) -4. `updatePartial` for **WriterBuilder** is deprecated. You can use ```update``` instead +4. `updatePartial` for **WriterBuilder** is deprecated. Use ```update``` instead -5. all derivation api is moved directly into core module in **tethys** package, including +5. all derivation api were moved directly into core module in **tethys** package, including * FieldStyle * WriterBuilder * ReaderBuilder -6. **auto** derivation is removed +6. **auto** derivation is deprecated. Use derives on toplevel type instead +# Scala 2 -### Quick start +## Quick start Add dependencies to your `build.sbt` ```scala diff --git a/build.sbt b/build.sbt index a15dbc9..82ea4bc 100644 --- a/build.sbt +++ b/build.sbt @@ -1,10 +1,6 @@ -lazy val scala212 = "2.12.19" -lazy val scala213 = "2.13.14" -/* FIXME -Return to use a stable version when 'scala.quoted.Quotes.reflectModuleSymbol.newClass' -and 'scala.quoted.Quotes.reflectModule.ClassDef.apply' are no longer experimental methods - */ -lazy val scala3 = "3.3.0" +lazy val scala212 = "2.12.20" +lazy val scala213 = "2.13.15" +lazy val scala3 = "3.3.4" ThisBuild / scalaVersion := scala3 @@ -195,7 +191,7 @@ lazy val circe = project .settings( name := "tethys-circe", libraryDependencies ++= Seq( - "io.circe" %% "circe-core" % "0.14.8" + "io.circe" %% "circe-core" % "0.14.10" ) ) .dependsOn(core, `jackson-212` % Test) @@ -223,7 +219,7 @@ lazy val enumeratum = project libraryDependencies ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, y)) => - Seq("com.beachape" %% "enumeratum" % "1.7.3") + Seq("com.beachape" %% "enumeratum" % "1.7.5") case _ => Seq.empty } } diff --git a/modules/core/src/main/scala-3/tethys/FieldStyle.scala b/modules/core/src/main/scala-3/tethys/FieldStyle.scala index ded1b2b..ae9abe4 100644 --- a/modules/core/src/main/scala-3/tethys/FieldStyle.scala +++ b/modules/core/src/main/scala-3/tethys/FieldStyle.scala @@ -10,7 +10,7 @@ enum FieldStyle { case SnakeCase, LowerSnakeCase, UpperSnakeCase, CapitalizedSnakeCase } -private[tethys] object FieldStyle: +object FieldStyle: private val regexp1: Pattern = Pattern.compile("([A-Z]+)([A-Z][a-z])") private val regexp2: Pattern = Pattern.compile("([a-z\\d])([A-Z])") private val replacement: String = "$1_$2" @@ -22,7 +22,7 @@ private[tethys] object FieldStyle: private val lowercase: String => String = _.toLowerCase() private val uppercase: String => String = _.toUpperCase() - def applyStyle(string: String, style: FieldStyle): String = + private[tethys] def applyStyle(string: String, style: FieldStyle): String = style match case FieldStyle.Capitalize => capitalize(string) case FieldStyle.Uncapitalize => uncapitalize(string) diff --git a/modules/core/src/main/scala-3/tethys/derivation/ConfigurationMacroUtils.scala b/modules/core/src/main/scala-3/tethys/derivation/ConfigurationMacroUtils.scala index 2ec0bae..7f57a5d 100644 --- a/modules/core/src/main/scala-3/tethys/derivation/ConfigurationMacroUtils.scala +++ b/modules/core/src/main/scala-3/tethys/derivation/ConfigurationMacroUtils.scala @@ -752,17 +752,19 @@ trait ConfigurationMacroUtils: case '[t *: ts] => TypeRepr.of[t] :: typeReprsOf[ts] def getAllChildren(tpe: TypeRepr): List[TypeRepr] = - tpe.asType match - case '[t] => - Expr.summon[scala.deriving.Mirror.Of[t]] match - case Some('{ - $m: scala.deriving.Mirror.SumOf[t] { - type MirroredElemTypes = subs - } - }) => - typeReprsOf[subs].flatMap(getAllChildren) - case _ => - List(tpe) + def loop(tpe: TypeRepr): List[TypeRepr] = + tpe.asType match + case '[t] => + Expr.summon[scala.deriving.Mirror.Of[t]] match + case Some('{ + $m: scala.deriving.Mirror.SumOf[t] { + type MirroredElemTypes = subs + } + }) => + typeReprsOf[subs].flatMap(loop) + case _ => + List(tpe) + loop(tpe).distinct case class SelectedField(name: String, selector: Term) @@ -1148,6 +1150,18 @@ trait ConfigurationMacroUtils: Some(FieldStyle.CapitalizedSnakeCase) case '{ tethys.derivation.builder.FieldStyle.capitalizedSnakeCase } => Some(FieldStyle.CapitalizedSnakeCase) + case '{ tethys.derivation.builder.FieldStyle.snakecase } => + Some(FieldStyle.SnakeCase) + case '{ tethys.derivation.builder.FieldStyle.kebabcase } => + Some(FieldStyle.KebabCase) + case '{ tethys.derivation.builder.FieldStyle.lowerSnakecase } => + Some(FieldStyle.LowerSnakeCase) + case '{ tethys.derivation.builder.FieldStyle.lowerKebabcase } => + Some(FieldStyle.LowerKebabCase) + case '{ tethys.derivation.builder.FieldStyle.upperSnakecase } => + Some(FieldStyle.UpperSnakeCase) + case '{ tethys.derivation.builder.FieldStyle.upperKebabcase } => + Some(FieldStyle.UpperKebabCase) case _ => None @deprecated diff --git a/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala b/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala index 688c9bc..300eacc 100644 --- a/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala +++ b/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala @@ -398,7 +398,7 @@ private[derivation] class DerivationMacro(val quotes: Quotes) } ReaderError.wrongJson( "Can not extract fields from json: " + uninitializedFields - .mkString(", ") + .mkString("'", "', '", "'") )(${ fieldName }) } diff --git a/modules/core/src/main/scala-3/tethys/derivation/builder/FieldStyle.scala b/modules/core/src/main/scala-3/tethys/derivation/builder/FieldStyle.scala index 8b29e5c..bad9393 100644 --- a/modules/core/src/main/scala-3/tethys/derivation/builder/FieldStyle.scala +++ b/modules/core/src/main/scala-3/tethys/derivation/builder/FieldStyle.scala @@ -13,4 +13,7 @@ enum FieldStyle { case capitalize, uncapitalize, lowercase, uppercase case kebabCase, lowerKebabCase, upperKebabCase, capitalizedKebabCase case snakeCase, lowerSnakeCase, upperSnakeCase, capitalizedSnakeCase + + case kebabcase, lowerKebabcase, upperKebabcase + case snakecase, lowerSnakecase, upperSnakecase } diff --git a/modules/core/src/test/scala-3/tethys/derivation/DerivationSpec.scala b/modules/core/src/test/scala-3/tethys/derivation/DerivationSpec.scala index 981dac1..5571234 100644 --- a/modules/core/src/test/scala-3/tethys/derivation/DerivationSpec.scala +++ b/modules/core/src/test/scala-3/tethys/derivation/DerivationSpec.scala @@ -20,6 +20,15 @@ class DerivationSpec extends AnyFlatSpec with Matchers { res } + it should "build message correctly" in { + case class Foo(bar: Int, baz: String, faz: Boolean) derives JsonReader + + util.Try(read[Foo](obj("bar" -> 1))) should matchPattern { + case util.Failure(ex) + if ex.getMessage == """Illegal json at '[ROOT]': Can not extract fields from json: 'baz', 'faz'""" => + } + } + it should "compile and correctly write and read product" in { case class Person( id: Int, diff --git a/modules/macro-derivation/src/main/scala-2/tethys/derivation/impl/derivation/ReaderDerivation.scala b/modules/macro-derivation/src/main/scala-2/tethys/derivation/impl/derivation/ReaderDerivation.scala index 2709dc0..37a7e40 100644 --- a/modules/macro-derivation/src/main/scala-2/tethys/derivation/impl/derivation/ReaderDerivation.scala +++ b/modules/macro-derivation/src/main/scala-2/tethys/derivation/impl/derivation/ReaderDerivation.scala @@ -459,7 +459,7 @@ trait ReaderDerivation if($predicate) { val $uninitializedFields = new scala.collection.mutable.ArrayBuffer[String](${xs.size}) ..$fields - $readerErrorCompanion.wrongJson("Can not extract fields from json" + $uninitializedFields.mkString("'", "', '", "'")) + $readerErrorCompanion.wrongJson("Can not extract fields from json: " + $uninitializedFields.mkString("'", "', '", "'")) } """ } diff --git a/modules/macro-derivation/src/test/scala-2.13+/tethys/derivation/SemiautoReaderDerivationTest.scala b/modules/macro-derivation/src/test/scala-2.13+/tethys/derivation/SemiautoReaderDerivationTest.scala index e1ea2cc..88faccb 100644 --- a/modules/macro-derivation/src/test/scala-2.13+/tethys/derivation/SemiautoReaderDerivationTest.scala +++ b/modules/macro-derivation/src/test/scala-2.13+/tethys/derivation/SemiautoReaderDerivationTest.scala @@ -21,6 +21,17 @@ class SemiautoReaderDerivationTest extends AnyFlatSpec with Matchers { behavior of "semiauto derivation" + + it should "build message correctly" in { + case class Foo(bar: Int, baz: String, faz: Boolean) + + implicit val reader: JsonReader[Foo] = tethys.derivation.semiauto.jsonReader + + util.Try(read[Foo](obj("bar" -> 1))) should matchPattern { + case util.Failure(ex) if ex.getMessage == """Illegal json at '[ROOT]': Can not extract fields from json: 'baz', 'faz'""" => + } + } + it should "derive readers for simple case class hierarchy" in { implicit val dReader: JsonReader[D] = jsonReader[D] implicit val cReader: JsonReader[C] = jsonReader[C] diff --git a/modules/macro-derivation/src/test/scala-3/tethys/derivation/SemiautoReaderDerivationTest.scala b/modules/macro-derivation/src/test/scala-3/tethys/derivation/SemiautoReaderDerivationTest.scala index 573a03d..27d164b 100644 --- a/modules/macro-derivation/src/test/scala-3/tethys/derivation/SemiautoReaderDerivationTest.scala +++ b/modules/macro-derivation/src/test/scala-3/tethys/derivation/SemiautoReaderDerivationTest.scala @@ -435,6 +435,26 @@ class SemiautoReaderDerivationTest extends AnyFlatSpec with Matchers { ) } + it should "derive reader for fieldStyle from description 3" in { + given JsonReader[CamelCaseNames] = JsonReader.derived[CamelCaseNames] { + ReaderDerivationConfig.empty.withFieldStyle( + tethys.derivation.builder.FieldStyle.lowerSnakecase + ) + } + + read[CamelCaseNames]( + obj( + "some_param" -> 1, + "id_param" -> 2, + "simple" -> 3 + ) + ) shouldBe CamelCaseNames( + someParam = 1, + IDParam = 2, + simple = 3 + ) + } + it should "derive strict reader" in { implicit val reader: JsonReader[CamelCaseNames] = jsonReader[CamelCaseNames]( diff --git a/project/plugins.sbt b/project/plugins.sbt index c98165c..30e2bfd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.7") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")