diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64c9824..ce6c55f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,4 +11,6 @@ permissions: jobs: release: uses: evolution-gaming/scala-github-actions/.github/workflows/release.yml@999c04ccae0d79a8a0c12ed7d71136762b2ae8c1 # v7.1.0 + with: + verify_sbt_command: 'cleanFull; +check; +all testFull package' secrets: inherit diff --git a/build.sbt b/build.sbt index 8fb8ee7..33552a0 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,5 @@ import Dependencies.* -import scala.collection.Seq - val Scala213 = "2.13.16" val Scala3 = "3.3.8" @@ -39,9 +37,9 @@ ThisBuild / versionPolicyIntention := Compatibility.BinaryCompatible val alias: Seq[sbt.Def.Setting[?]] = // the Scala version is left to the caller, since CI runs `check` once per version in its matrix - addCommandAlias("check", "all scalafmtCheckRepo versionPolicyCheck Compile/doc") ++ + addCommandAlias("check", "; scalafmtCheckRepo; all versionPolicyCheck Compile/doc") ++ addCommandAlias("fmt", "scalafmtRepo") ++ - addCommandAlias("build", "+all compile test") + addCommandAlias("build", "+all compile testFull") lazy val root = project .in(file(".")) @@ -50,7 +48,7 @@ lazy val root = project .settings( commonSettings, publish / skip := true, - name := "play-json-tools" + name := "play-json-tools " ) .aggregate( `play-json-tools`, @@ -84,6 +82,17 @@ lazy val `play-json-generic` = crossProject(JVMPlatform, JSPlatform) Seq() })).map(excludeLog4j) ) + .jsSettings( + coverageEnabled := false, + versionPolicyIgnored ++= Seq( + "org.scala-lang" %% "scala3-library", + "com.chuusai" %% "shapeless", + "org.playframework" %% "play-json", + "org.playframework" %% "play-functional", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" + ) + ) lazy val `play-json-tools` = project .settings( @@ -107,24 +116,39 @@ lazy val `play-json-jsoniter` = crossProject(JVMPlatform, JSPlatform) commonSettings, allowUnsafeScalaLibUpgrade := true, libraryDependencies ++= - (Seq( + Seq( playJson, jackson.core, jackson.databind, jsoniter, collectionCompact, scalaTest % Test - ) ++ - (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, _)) => - Seq( - jsonGenerator % Test, - jsonValues % Test, - scalacheck % Test - ) - case _ => - Seq() - })).map(excludeLog4j) + ).map(excludeLog4j) + ) + .jsSettings( + coverageEnabled := false, + versionPolicyIgnored ++= Seq( + "org.scala-lang" %% "scala3-library", + "org.playframework" %% "play-json", + "org.playframework" %% "play-functional", + "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core", + "org.scala-lang.modules" %% "scala-collection-compat", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8", + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" + ) + ) + .jvmSettings( + libraryDependencies ++= + (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, _)) => + Seq( + jsonGenerator % Test, + jsonValues % Test, + scalacheck % Test + ) + case _ => + Seq() + }).map(excludeLog4j) ) // not part of the aggregate, benchmarks are run manually diff --git a/play-json-generic/src/main/scala/com/evolution/playjson/generic/NameCodingStrategy.scala b/play-json-generic/src/main/scala/com/evolution/playjson/generic/NameCodingStrategy.scala index d074f99..0436ab5 100644 --- a/play-json-generic/src/main/scala/com/evolution/playjson/generic/NameCodingStrategy.scala +++ b/play-json-generic/src/main/scala/com/evolution/playjson/generic/NameCodingStrategy.scala @@ -13,7 +13,7 @@ object NameCodingStrategy extends LowPriority object NameCodingStrategies { private def lowerCaseSepCoding(sep: String): NameCodingStrategy = new NameCodingStrategy() { - override def apply(s: String): String = s.split("(?, 2011-2021 Lightbend Inc. + */ + +/* + * Copied from play-json 3.0.6 `play-json/jvm/src/main/scala/play/api/libs/json/JsonConfig.scala` with deprecated members dropped. + * play-json keeps it in the JVM-only sources, so its Scala.js artifact lacks it, while the shared `JsonValueCodecJsValue` needs it. + */ + +package play.api.libs.json + +import play.api.libs.json.JsonConfig.defaultMaxPlain +import play.api.libs.json.JsonConfig.defaultMinPlain +import play.api.libs.json.JsonConfig.defaultDigitsLimit +import play.api.libs.json.JsonConfig.defaultMathContext +import play.api.libs.json.JsonConfig.defaultPreserveZeroDecimal +import play.api.libs.json.JsonConfig.defaultScaleLimit + +import java.math.MathContext + +import scala.util.control.NonFatal + +/** Parse and serialization settings for BigDecimals. Defines limits that will be used when parsing the BigDecimals, + * like how many digits are accepted. + */ +sealed trait BigDecimalParseConfig { + + /** The `MathContext` used when parsing, which will be "decimal32", "decimal64", "decimal128" (default), + * or "unlimited". + * This can be set using the [[JsonConfig.mathContextProperty]] system property. + */ + def mathContext: MathContext + + /** Limits the scale, and it is related to the math context used. + * The default value is [[JsonConfig.defaultScaleLimit]]. + * This can be set using the [[JsonConfig.scaleLimitProperty]] system property. + */ + def scaleLimit: Int + + /** How many digits are accepted, also related to the math context used. + * The default value is [[JsonConfig.defaultDigitsLimit]]. + * This can be set using the [[JsonConfig.digitsLimitProperty]] system property. + */ + def digitsLimit: Int +} + +object BigDecimalParseConfig { + def apply( + mathContext: MathContext = defaultMathContext, + scaleLimit: Int = defaultScaleLimit, + digitsLimit: Int = defaultDigitsLimit + ): BigDecimalParseConfig = BigDecimalParseConfigImpl(mathContext, scaleLimit, digitsLimit) +} + +private final case class BigDecimalParseConfigImpl(mathContext: MathContext, scaleLimit: Int, digitsLimit: Int) + extends BigDecimalParseConfig + +sealed trait BigDecimalSerializerConfig { + + /** Minimum magnitude of BigDecimal to write out as a plain string. + * Defaults to [[JsonConfig.defaultMinPlain]]. + * This can be set using the [[JsonConfig.minPlainProperty]] system property. + */ + def minPlain: BigDecimal + + /** Maximum magnitude of BigDecimal to write out as a plain string. + * Defaults to [[JsonConfig.defaultMaxPlain]]. + * This can be set using the [[JsonConfig.maxPlainProperty]] system property. + */ + def maxPlain: BigDecimal + + /** True to preserve a zero decimal , or false to drop them (the default). + * For example, 1.00 will be serialized as 1 if false or 1.0 if true (only a single zero is preserved). + * Other trailing zeroes will be dropped regardless of this value. + * For example, 1.1000 will always be serialized as 1.1. + * This can be set using the [[JsonConfig.preserveZeroDecimalProperty]] system property. + */ + def preserveZeroDecimal: Boolean +} + +object BigDecimalSerializerConfig { + def apply( + minPlain: BigDecimal = defaultMinPlain, + maxPlain: BigDecimal = defaultMaxPlain, + preserveZeroDecimal: Boolean = defaultPreserveZeroDecimal + ): BigDecimalSerializerConfig = + DecimalSerializerSettingsImpl(minPlain, maxPlain, preserveZeroDecimal) +} + +private final case class DecimalSerializerSettingsImpl( + minPlain: BigDecimal, + maxPlain: BigDecimal, + preserveZeroDecimal: Boolean +) extends BigDecimalSerializerConfig + +sealed trait JsonConfig { + def bigDecimalParseConfig: BigDecimalParseConfig + def bigDecimalSerializerConfig: BigDecimalSerializerConfig +} + +object JsonConfig { + + /** The default math context ("decimal128"). + */ + val defaultMathContext: MathContext = MathContext.DECIMAL128 + + /** The default limit for the scale considering the default MathContext of decimal128. + * limit for scale for decimal128: BigDecimal("0." + "0" * 33 + "1e-6143", java.math.MathContext.DECIMAL128).scale + 1 + */ + val defaultScaleLimit: Int = 6178 + + /** The default limit for digits considering the default MathContext of decimal128. + * 307 digits should be the correct value for 128 bytes. But we are using 310 + * because Play JSON uses BigDecimal to parse any number including Doubles and + * Doubles max value has 309 digits, so we are using 310 here + */ + val defaultDigitsLimit: Int = 310 + + /** Zero decimal values (e.g. .0 or .00) or dropped by default. + * For example, a value of 1.0 or 1.00 will be serialized as 1. + */ + val defaultPreserveZeroDecimal: Boolean = false + + /** The default maximum magnitude of BigDecimal to write out as a plain string. + */ + val defaultMaxPlain: BigDecimal = 1e20 + + /** The default minimum magnitude of BigDecimal to write out as a plain string. + */ + val defaultMinPlain: BigDecimal = 1e-10 + + /** The system property to override the scale limit. + */ + val scaleLimitProperty: String = "play.json.parser.scaleLimit" + + /** The system property to override the digits limit + */ + val digitsLimitProperty: String = "play.json.parser.digitsLimit" + + /** The system property to override the math context. This can be "decimal32", "decimal64", "decimal128" (the default), + * or "unlimited". + */ + val mathContextProperty: String = "play.json.parser.mathContext" + + /** The system property to override the minimum magnitude of BigDecimal to write out as a plain string + */ + val minPlainProperty: String = "play.json.serializer.minPlain" + + /** The system property to override the maximum magnitude of BigDecimal to write out as a plain string + */ + val maxPlainProperty: String = "play.json.serializer.maxPlain" + + /** The system property to override whether zero decimals (e.g. .0 or .00) are written by default. These are dropped by default. + */ + val preserveZeroDecimalProperty: String = "play.json.serializer.preserveZeroDecimal" + + private[json] def loadScaleLimit: Int = prop(scaleLimitProperty, defaultScaleLimit)(_.toInt) + + private[json] def loadDigitsLimit: Int = prop(digitsLimitProperty, defaultDigitsLimit)(_.toInt) + + private[json] def loadMathContext: MathContext = parseMathContext(mathContextProperty) + + private[json] def loadMinPlain: BigDecimal = prop(minPlainProperty, defaultMinPlain)(BigDecimal.exact) + + private[json] def loadMaxPlain: BigDecimal = prop(maxPlainProperty, defaultMaxPlain)(BigDecimal.exact) + + private[json] def loadPreserveZeroDecimal: Boolean = + prop(preserveZeroDecimalProperty, defaultPreserveZeroDecimal)(_.toBoolean) + + // Default settings, which can be controlled with system properties. + val settings: JsonConfig = + JsonConfig( + BigDecimalParseConfig(loadMathContext, loadScaleLimit, loadDigitsLimit), + BigDecimalSerializerConfig(loadMinPlain, loadMaxPlain, loadPreserveZeroDecimal) + ) + + def apply(): JsonConfig = apply(BigDecimalParseConfig(), BigDecimalSerializerConfig()) + + def apply( + bigDecimalParseConfig: BigDecimalParseConfig, + bigDecimalSerializerConfig: BigDecimalSerializerConfig + ): JsonConfig = + JsonConfigImpl(bigDecimalParseConfig, bigDecimalSerializerConfig) + + private[json] def parseMathContext(key: String): MathContext = sys.props.get(key).map(_.toLowerCase) match { + case Some("decimal128") => MathContext.DECIMAL128 + case Some("decimal64") => MathContext.DECIMAL64 + case Some("decimal32") => MathContext.DECIMAL32 + case Some("unlimited") => MathContext.UNLIMITED + case _ => defaultMathContext + } + + private[json] def prop[T](key: String, default: T)(f: String => T): T = + try { + sys.props.get(key).map(f).getOrElse(default) + } catch { + case NonFatal(_) => default + } +} + +private final case class JsonConfigImpl( + bigDecimalParseConfig: BigDecimalParseConfig, + bigDecimalSerializerConfig: BigDecimalSerializerConfig +) extends JsonConfig diff --git a/play-json-jsoniter/shared/src/test/scala/com/evolution/playjson/jsoniter/JavaTimeFormatsSpec.scala b/play-json-jsoniter/jvm/src/test/scala/com/evolution/playjson/jsoniter/JavaTimeFormatsSpec.scala similarity index 100% rename from play-json-jsoniter/shared/src/test/scala/com/evolution/playjson/jsoniter/JavaTimeFormatsSpec.scala rename to play-json-jsoniter/jvm/src/test/scala/com/evolution/playjson/jsoniter/JavaTimeFormatsSpec.scala diff --git a/play-json-jsoniter/shared/src/test/scala/com/evolution/playjson/jsoniter/JsoniterSpec.scala b/play-json-jsoniter/jvm/src/test/scala/com/evolution/playjson/jsoniter/JsoniterSpec.scala similarity index 100% rename from play-json-jsoniter/shared/src/test/scala/com/evolution/playjson/jsoniter/JsoniterSpec.scala rename to play-json-jsoniter/jvm/src/test/scala/com/evolution/playjson/jsoniter/JsoniterSpec.scala diff --git a/play-json-jsoniter/shared/src/test/scala/com/evolution/playjson/jsoniter/TestData.scala b/play-json-jsoniter/jvm/src/test/scala/com/evolution/playjson/jsoniter/TestData.scala similarity index 100% rename from play-json-jsoniter/shared/src/test/scala/com/evolution/playjson/jsoniter/TestData.scala rename to play-json-jsoniter/jvm/src/test/scala/com/evolution/playjson/jsoniter/TestData.scala diff --git a/project/build.properties b/project/build.properties index d8f4c25..d0a88b6 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.13.0 \ No newline at end of file +sbt.version = 2.0.10 \ No newline at end of file