diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc4a19969..0684ee535 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,131 +1,202 @@ -name: CI +# This file was autogenerated using `zio-sbt-ci` plugin via `sbt ciGenerateGithubWorkflow` +# task and should be included in the git repository. Please do not edit it manually. +name: CI env: - JDK_JAVA_OPTIONS: -XX:+PrintCommandLineFlags # JDK_JAVA_OPTIONS is _the_ env. variable to use for modern Java - JVM_OPTS: -XX:+PrintCommandLineFlags # for Java 8 only (sadly, it is not modern enough for JDK_JAVA_OPTIONS) - -on: - pull_request: - push: - branches: ['series/2.x'] + JDK_JAVA_OPTIONS: -XX:+PrintCommandLineFlags +'on': + workflow_dispatch: {} release: types: - published - + pull_request: + branches-ignore: + - gh-pages + push: + branches: + - series/2.x +concurrency: + group: ${{ github.workflow }}-${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.run_id || github.ref }} + cancel-in-progress: true jobs: - lint: - runs-on: ubuntu-20.04 - timeout-minutes: 30 + build: + name: Build + runs-on: ubuntu-latest + continue-on-error: true steps: - - name: Checkout current branch - uses: actions/checkout@v4.1.2 + - name: Git Checkout + uses: actions/checkout@v4 with: - fetch-depth: 0 - - name: Setup Java - uses: actions/setup-java@v4.2.1 + fetch-depth: '0' + - name: Install libuv + run: sudo apt-get update && sudo apt-get install -y libuv1-dev + - name: Setup Scala + uses: actions/setup-java@v4 with: - distribution: temurin - java-version: 21 + distribution: corretto + java-version: '17' check-latest: true - - name: Cache scala dependencies + - name: Cache Dependencies uses: coursier/cache-action@v6 - - name: Lint code - run: sbt check - - benchmarks: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - java: ['17', '21'] - scala: ['2.13.15'] + - name: Check all code compiles + run: sbt +Test/compile + - name: Check artifacts build process + run: sbt +publishLocal + - name: Check website build process + run: sbt docs/clean; sbt docs/buildWebsite + lint: + name: Lint + runs-on: ubuntu-latest + continue-on-error: false steps: - - name: Checkout current branch - uses: actions/checkout@v4.1.2 + - name: Git Checkout + uses: actions/checkout@v4 with: - fetch-depth: 0 - - name: Setup Java - uses: actions/setup-java@v4.2.1 - with: - distribution: temurin - java-version: ${{ matrix.java }} - check-latest: true - - name: Cache scala dependencies - uses: coursier/cache-action@v6 - - name: Compile benchmarks - run: sbt ++${{ matrix.scala }}! jmh:compile - - mdoc: - runs-on: ubuntu-20.04 - timeout-minutes: 60 - steps: - - name: Checkout current branch - uses: actions/checkout@v4.1.2 - - name: Setup Java - uses: actions/setup-java@v4.2.1 + fetch-depth: '0' + - name: Install libuv + run: sudo apt-get update && sudo apt-get install -y libuv1-dev + - name: Setup Scala + uses: actions/setup-java@v4 with: - distribution: temurin - java-version: 8 + distribution: corretto + java-version: '17' check-latest: true - - name: Cache scala dependencies + - name: Cache Dependencies uses: coursier/cache-action@v6 - - name: Check Document Generation - run: sbt compileDocs - + - name: Check if the site workflow is up to date + run: sbt ciCheckGithubWorkflow + - name: Lint + run: sbt lint test: - runs-on: ubuntu-20.04 - timeout-minutes: 30 + name: Test + runs-on: ubuntu-latest + continue-on-error: false strategy: - fail-fast: false matrix: - java: ['17', '21'] - scala: ['2.12.19', '2.13.15', '3.3.4'] - platform: ['JVM', 'JS', 'Native'] + java: + - '11' + - '17' + - '21' + scalaVersion: + - 2.12.20 + - 2.13.15 + - 3.3.4 + scalaPlatform: + - JS + - JVM + - Native + fail-fast: false steps: - - name: Checkout current branch - uses: actions/checkout@v4.1.2 - with: - fetch-depth: 0 - - name: Setup Java - uses: actions/setup-java@v4.2.1 + - name: Install libuv + run: sudo apt-get update && sudo apt-get install -y libuv1-dev + - name: Setup Scala + uses: actions/setup-java@v4 with: - distribution: temurin + distribution: corretto java-version: ${{ matrix.java }} check-latest: true - - name: Cache scala dependencies + - name: Cache Dependencies uses: coursier/cache-action@v6 + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: Test + run: sbt +test${{ matrix.scalaPlatform }}${{ startsWith(matrix.scalaVersion, '2.12') && '2_12' || (startsWith(matrix.scalaVersion, '2.13') && '2_13' || (startsWith(matrix.scalaVersion, '3') && '3' || '')) }} + update-readme: + name: Update README + runs-on: ubuntu-latest + continue-on-error: false + if: ${{ github.event_name == 'push' }} + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: '0' - name: Install libuv - if: matrix.platform == 'Native' run: sudo apt-get update && sudo apt-get install -y libuv1-dev - - name: Run Macros tests - if: ${{ !startsWith(matrix.scala, '3.3.') }} - run: sbt ++${{ matrix.scala }}! testScala2${{ matrix.platform }} - - name: Run tests - run: sbt ++${{ matrix.scala }}! test${{ matrix.platform }} + - name: Setup Scala + uses: actions/setup-java@v4 + with: + distribution: corretto + java-version: '17' + check-latest: true + - name: Cache Dependencies + uses: coursier/cache-action@v6 + - name: Generate Readme + run: sbt docs/generateReadme + - name: Commit Changes + run: | + git config --local user.email "zio-assistant[bot]@users.noreply.github.com" + git config --local user.name "ZIO Assistant" + git add README.md + git commit -m "Update README.md" || echo "No changes to commit" + - name: Generate Token + id: generate-token + uses: zio/generate-github-app-token@v1.0.0 + with: + app_id: ${{ secrets.APP_ID }} + app_private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v6 + with: + body: |- + Autogenerated changes after running the `sbt docs/generateReadme` command of the [zio-sbt-website](https://zio.dev/zio-sbt) plugin. + I will automatically update the README.md file whenever there is new change for README.md, e.g. + - After each release, I will update the version in the installation section. + - After any changes to the "docs/index.md" file, I will update the README.md file accordingly. + branch: zio-sbt-website/update-readme + commit-message: Update README.md + token: ${{ steps.generate-token.outputs.token }} + delete-branch: true + title: Update README.md + - name: Approve PR + if: ${{ steps.cpr.outputs.pull-request-number }} + run: gh pr review "$PR_URL" --approve + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} + - name: Enable Auto-Merge + if: ${{ steps.cpr.outputs.pull-request-number }} + run: gh pr merge --auto --squash "$PR_URL" || gh pr merge --squash "$PR_URL" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ steps.cpr.outputs.pull-request-url }} ci: - runs-on: ubuntu-20.04 - needs: [lint, mdoc, benchmarks, test] + name: ci + runs-on: ubuntu-latest + continue-on-error: false + needs: + - lint + - test + - build steps: - - name: Aggregate of lint, and all tests + - name: Report Successful CI run: echo "ci passed" - - publish: - runs-on: ubuntu-20.04 - timeout-minutes: 60 - needs: [ci] - if: github.event_name != 'pull_request' + release: + name: Release + runs-on: ubuntu-latest + continue-on-error: false + needs: + - ci + if: ${{ github.event_name != 'pull_request' }} steps: - - name: Checkout current branch - uses: actions/checkout@v4.1.2 + - name: Git Checkout + uses: actions/checkout@v4 with: - fetch-depth: 0 - - name: Setup Java - uses: actions/setup-java@v4.2.1 + fetch-depth: '0' + - name: Install libuv + run: sudo apt-get update && sudo apt-get install -y libuv1-dev + - name: Setup Scala + uses: actions/setup-java@v4 with: - distribution: temurin - java-version: 8 + distribution: corretto + java-version: '17' check-latest: true + - name: Cache Dependencies + uses: coursier/cache-action@v6 - name: Release run: sbt ci-release env: @@ -133,3 +204,64 @@ jobs: PGP_SECRET: ${{ secrets.PGP_SECRET }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + CI_RELEASE: +publishSigned + CI_SNAPSHOT_RELEASE: +publish + release-docs: + name: Release Docs + runs-on: ubuntu-latest + continue-on-error: false + needs: + - release + if: ${{ ((github.event_name == 'release') && (github.event.action == 'published')) || (github.event_name == 'workflow_dispatch') }} + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: Install libuv + run: sudo apt-get update && sudo apt-get install -y libuv1-dev + - name: Setup Scala + uses: actions/setup-java@v4 + with: + distribution: corretto + java-version: '17' + check-latest: true + - name: Cache Dependencies + uses: coursier/cache-action@v6 + - name: Setup NodeJs + uses: actions/setup-node@v4 + with: + node-version: 16.x + registry-url: https://registry.npmjs.org + - name: Publish Docs to NPM Registry + run: sbt docs/publishToNpm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + notify-docs-release: + name: Notify Docs Release + runs-on: ubuntu-latest + continue-on-error: false + needs: + - release-docs + if: ${{ (github.event_name == 'release') && (github.event.action == 'published') }} + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + fetch-depth: '0' + - name: notify the main repo about the new release of docs package + run: | + PACKAGE_NAME=$(cat docs/package.json | grep '"name"' | awk -F'"' '{print $4}') + PACKAGE_VERSION=$(npm view $PACKAGE_NAME version) + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: token ${{ secrets.PAT_TOKEN }}"\ + https://api.github.com/repos/zio/zio/dispatches \ + -d '{ + "event_type":"update-docs", + "client_payload":{ + "package_name":"'"${PACKAGE_NAME}"'", + "package_version": "'"${PACKAGE_VERSION}"'" + } + }' diff --git a/.scalafix.conf b/.scalafix.conf index 87a944cca..6af2e8e17 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -23,5 +23,6 @@ OrganizeImports { } RemoveUnused { - imports = false // handled by OrganizeImports + # handled by OrganizeImports + imports = false } diff --git a/.scalafmt.conf b/.scalafmt.conf index 46233e07a..3189b9dc8 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,10 +1,33 @@ -version = "2.7.5" +version = "3.8.3" +runner.dialect = scala213 + +fileOverride { + "glob:**/project/**" { + runner.dialect = scala3 + } + "glob:**/build.sbt" { + runner.dialect = scala213 + } + "glob:**/scala-2/**" { + runner.dialect = scala213 + } + "glob:**/scala-2.12/**" { + runner.dialect = scala212 + } + "glob:**/scala-2.13/**" { + runner.dialect = scala213 + } + "glob:**/scala-3/**" { + runner.dialect = scala3 + } +} + maxColumn = 120 align.preset = most align.multiline = false continuationIndent.defnSite = 2 assumeStandardLibraryStripMargin = true -docstrings = JavaDoc +docstrings.style = Asterisk lineEndings = preserve includeCurlyBraceInSelectChains = false danglingParentheses.preset = true diff --git a/README.md b/README.md index 171044685..4835d0251 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The goal of this project is to create the best all-round JSON library for Scala: In order to use this library, we need to add the following line in our `build.sbt` file: ```scala -libraryDependencies += "dev.zio" %% "zio-json" % "0.6.2" +libraryDependencies += "dev.zio" %% "zio-json" % "0.7.3" ``` ## Example diff --git a/build.sbt b/build.sbt index 89a3a8eff..2df09ab13 100644 --- a/build.sbt +++ b/build.sbt @@ -1,14 +1,18 @@ -import BuildHelper._ import explicitdeps.ExplicitDepsPlugin.autoImport.moduleFilterRemoveValue import sbtcrossproject.CrossPlugin.autoImport.crossProject Global / onChangedBuildSource := IgnoreSourceChanges +enablePlugins(ZioSbtEcosystemPlugin, ZioSbtCiPlugin) + +usefulTasksAndSettings ++= BuildHelper.usefulTasksAndSettings.value + inThisBuild( List( organization := "dev.zio", - homepage := Some(url("https://zio.dev/zio-json/")), - licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), + name := "zio-json", + homepage := Some(url("https://zio.dev/zio-json/")), + licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), developers := List( Developer( "jdegoes", @@ -16,52 +20,18 @@ inThisBuild( "john@degoes.net", url("http://degoes.net") ) - ) + ), + scalaVersion := scala213.value, + javaPlatform := zio.sbt.JavaVersion.`11`, + ciEnabledBranches := Seq("series/2.x"), + checkMima / skip := true ) ) -addCommandAlias("check", "; scalafmtSbtCheck; scalafmtCheckAll") -addCommandAlias("fmt", "all scalafmtSbt scalafmtAll") -addCommandAlias("fmtCheck", "all scalafmtSbtCheck scalafmtCheckAll") -addCommandAlias("prepare", "fmt") - -addCommandAlias( - "testJVM", - "zioJsonJVM/test; zioJsonYaml/test; zioJsonInteropHttp4s/test; zioJsonInteropScalaz7xJVM/test; zioJsonGolden/test" -) - -addCommandAlias( - "testScala2JVM", - "zioJsonMacrosJVM/test; zioJsonInteropRefinedJVM/test" -) - -addCommandAlias( - "testScala2JS", - "zioJsonMacrosJS/test; zioJsonInteropRefinedJS/test" -) - -addCommandAlias( - "testScala2Native", - "zioJsonMacrosNative/test; zioJsonInteropRefinedNative/test" -) - -addCommandAlias( - "testJS", - "zioJsonJS/test; zioJsonInteropScalaz7xJS/test" -) - -addCommandAlias( - "testNative", - "zioJsonNative/test; zioJsonInteropScalaz7xNative/test" -) - -val zioVersion = "2.1.7" - lazy val zioJsonRoot = project .in(file(".")) .settings( - publish / skip := true, - unusedCompileDependenciesFilter -= moduleFilter("org.scala-js", "scalajs-library") + publish / skip := true ) .aggregate( docs, @@ -82,53 +52,50 @@ lazy val zioJsonRoot = project zioJsonGolden ) -val circeVersion = "0.14.10" - lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("zio-json")) - .settings(stdSettings("zio-json")) + .settings(stdSettings(Some("zio-json"), turnCompilerWarningIntoErrors = false)) .settings(crossProjectSettings) .settings(buildInfoSettings("zio.json")) .enablePlugins(NeoJmhPlugin) .settings( - scalacOptions -= "-Xfatal-warnings", // not quite ready. - // as per @fommil, optimization slows things down. scalacOptions -= "-opt:l:inline", scalacOptions -= "-opt-inline-from:zio.internal.**", Test / scalacOptions ++= { - if (scalaVersion.value == ScalaDotty) + if (isScala3.value) Vector("-Yretain-trees") else Vector.empty }, libraryDependencies ++= Seq( - "dev.zio" %%% "zio" % zioVersion, - "dev.zio" %%% "zio-streams" % zioVersion, + "dev.zio" %%% "zio" % Version.zio, + "dev.zio" %%% "zio-streams" % Version.zio, "org.scala-lang.modules" %%% "scala-collection-compat" % "2.12.0", - "dev.zio" %%% "zio-test" % zioVersion % "test", - "dev.zio" %%% "zio-test-sbt" % zioVersion % "test", - "io.circe" %%% "circe-core" % circeVersion % "test", - "io.circe" %%% "circe-generic" % circeVersion % "test", - "io.circe" %%% "circe-parser" % circeVersion % "test" + "dev.zio" %%% "zio-test" % Version.zio % "test", + "dev.zio" %%% "zio-test-sbt" % Version.zio % "test", + "io.circe" %%% "circe-core" % Version.circe % "test", + "io.circe" %%% "circe-generic" % Version.circe % "test", + "io.circe" %%% "circe-parser" % Version.circe % "test" ), // scala version specific dependencies libraryDependencies ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((3, _)) => - Vector( - "com.softwaremill.magnolia1_3" %%% "magnolia" % "1.3.7" - ) - - case _ => - Vector( - "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, - "com.softwaremill.magnolia1_2" %%% "magnolia" % "1.1.10", - "io.circe" %%% "circe-generic-extras" % "0.14.4" % "test", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.30.9" % "test", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.30.9" % "test" - ) - } + if (isScala3.value) + Vector( + "com.softwaremill.magnolia1_3" %%% "magnolia" % Version.magnolia1_3, + "com.github.ghik" % s"silencer-lib_${scala213.value}" % Version.silencer % Provided + ) + else + Vector( + "com.github.ghik" % "silencer-lib" % Version.silencer % Provided cross CrossVersion.full, + compilerPlugin("com.github.ghik" % "silencer-plugin" % Version.silencer cross CrossVersion.full), + compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full), + "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, + "com.softwaremill.magnolia1_2" %%% "magnolia" % Version.magnolia1_2, + "io.circe" %%% "circe-generic-extras" % "0.14.4" % "test", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.30.9" % "test", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.30.9" % "test" + ) }, Compile / sourceGenerators += Def.task { val dir = (Compile / sourceManaged).value @@ -192,7 +159,7 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform) |}""".stripMargin ) Seq(file) - }.taskValue, + }, Compile / sourceGenerators += Def.task { val dir = (Compile / sourceManaged).value val file = dir / "zio" / "json" / "GeneratedTupleCodecs.scala" @@ -215,168 +182,156 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform) }.taskValue, inConfig(Jmh)(org.scalafmt.sbt.ScalafmtPlugin.scalafmtConfigSettings) ) - .settings(testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")) .jsSettings( - libraryDependencies ++= Seq( - "io.github.cquiroz" %%% "scala-java-time" % scalaJavaTimeVersion, - "io.github.cquiroz" %%% "scala-java-time-tzdb" % scalaJavaTimeVersion + jsSettings ++ Seq( + libraryDependencies ++= Seq( + "io.github.cquiroz" %%% "scala-java-time" % Version.scalaJavaTime, + "io.github.cquiroz" %%% "scala-java-time-tzdb" % Version.scalaJavaTime + ), + scalaJSUseMainModuleInitializer := true, + coverageEnabled := false + ) + ) + .jvmSettings(jvmSettings) + .nativeSettings( + nativeSettings ++ Seq( + libraryDependencies ++= Seq( + "io.github.cquiroz" %%% "scala-java-time" % Version.scalaJavaTime + ) ) ) .jvmSettings( libraryDependencies ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((3, _)) => - Vector( - "org.typelevel" %% "jawn-ast" % "1.6.0" % "test" - ) - - case Some((2, n)) => - if (n >= 13) { - Seq( - "com.particeep" %% "play-json-extensions" % "0.43.1" % "test", - "com.typesafe.play" %%% "play-json" % "2.9.4" % "test", - "org.typelevel" %% "jawn-ast" % "1.6.0" % "test" - ) - } else { - Seq( - "ai.x" %% "play-json-extensions" % "0.42.0" % "test", - "com.typesafe.play" %%% "play-json" % "2.9.4" % "test", - "org.typelevel" %% "jawn-ast" % "1.6.0" % "test" - ) - } - - case _ => - Seq.empty - } + if (isScala3.value) + Vector( + "org.typelevel" %% "jawn-ast" % "1.6.0" % "test" + ) + else if (isScala2_13.value) + Seq( + "com.particeep" %% "play-json-extensions" % "0.43.1" % "test", + "com.typesafe.play" %%% "play-json" % "2.9.4" % "test", + "org.typelevel" %% "jawn-ast" % "1.6.0" % "test" + ) + else if (isScala2_12.value) + Seq( + "ai.x" %% "play-json-extensions" % "0.42.0" % "test", + "com.typesafe.play" %%% "play-json" % "2.9.4" % "test", + "org.typelevel" %% "jawn-ast" % "1.6.0" % "test" + ) + else + Seq.empty } ) - .nativeSettings(Test / fork := false) - .nativeSettings( - libraryDependencies ++= Seq( - "io.github.cquiroz" %%% "scala-java-time" % scalaJavaTimeVersion - ), - nativeConfig ~= { _.withMultithreading(false) } - ) - .enablePlugins(BuildInfoPlugin) lazy val zioJsonJS = zioJson.js - .settings( - scalaJSUseMainModuleInitializer := true, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), - coverageEnabled := false - ) lazy val zioJsonJVM = zioJson.jvm lazy val zioJsonGolden = project .in(file("zio-json-golden")) - .settings(stdSettings("zio-json-golden")) + .settings(stdSettings(Some("zio-json-golden"), turnCompilerWarningIntoErrors = false)) .settings(buildInfoSettings("zio.json.golden")) .settings( libraryDependencies ++= Seq( - "dev.zio" %% "zio" % zioVersion, - "dev.zio" %% "zio-test" % zioVersion, - "dev.zio" %% "zio-test-sbt" % zioVersion, - "dev.zio" %% "zio-test-magnolia" % zioVersion - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + "dev.zio" %% "zio" % Version.zio, + "dev.zio" %% "zio-test" % Version.zio, + "dev.zio" %% "zio-test-sbt" % Version.zio, + "dev.zio" %% "zio-test-magnolia" % Version.zio + ) ) .dependsOn(zioJsonJVM) - .enablePlugins(BuildInfoPlugin) lazy val zioJsonYaml = project .in(file("zio-json-yaml")) - .settings(stdSettings("zio-json-yaml")) + .settings(stdSettings(Some("zio-json-yaml"))) .settings(buildInfoSettings("zio.json.yaml")) .settings( libraryDependencies ++= Seq( - "org.yaml" % "snakeyaml" % "2.3", - "dev.zio" %% "zio" % zioVersion, - "dev.zio" %% "zio-test" % zioVersion % "test", - "dev.zio" %% "zio-test-sbt" % zioVersion % "test" - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + "org.yaml" % "snakeyaml" % Version.snakeyaml, + "dev.zio" %% "zio" % Version.zio, + "dev.zio" %% "zio-test" % Version.zio % "test", + "dev.zio" %% "zio-test-sbt" % Version.zio % "test" + ) ) .dependsOn(zioJsonJVM) - .enablePlugins(BuildInfoPlugin) lazy val zioJsonMacros = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("zio-json-macros")) - .nativeConfigure(_.dependsOn(zioJson.native)) - .settings(stdSettings("zio-json-macros")) + .dependsOn(zioJson) + .settings(stdSettings(Some("zio-json-macros"), turnCompilerWarningIntoErrors = false)) .settings(crossProjectSettings) .settings(macroExpansionSettings) .settings( - crossScalaVersions -= ScalaDotty, - scalacOptions -= "-Xfatal-warnings", // not quite ready. + crossScalaVersions -= scala3.value, libraryDependencies ++= Seq( "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, - "dev.zio" %%% "zio-test" % zioVersion % "test", - "dev.zio" %%% "zio-test-sbt" % zioVersion % "test" - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), - nativeConfig ~= { _.withMultithreading(false) } + "dev.zio" %%% "zio-test" % Version.zio % "test", + "dev.zio" %%% "zio-test-sbt" % Version.zio % "test" + ) + ) + .jsSettings( + coverageEnabled := false ) - .nativeSettings(Test / fork := false) + .jsSettings(jsSettings) + .jvmSettings(jvmSettings) + .nativeSettings(nativeSettings) -lazy val zioJsonMacrosJVM = zioJsonMacros.jvm.dependsOn(zioJsonJVM) +lazy val zioJsonMacrosJVM = zioJsonMacros.jvm lazy val zioJsonMacrosJS = zioJsonMacros.js - .settings(coverageEnabled := false) - .dependsOn(zioJsonJS) lazy val zioJsonInteropHttp4s = project .in(file("zio-json-interop-http4s")) - .settings(stdSettings("zio-json-interop-http4s")) + .settings(stdSettings(Some("zio-json-interop-http4s"), turnCompilerWarningIntoErrors = false)) .settings(buildInfoSettings("zio.json.interop.http4s")) .settings( - crossScalaVersions -= ScalaDotty, libraryDependencies ++= Seq( - "org.http4s" %% "http4s-dsl" % "0.23.29", - "dev.zio" %% "zio" % zioVersion, - "org.typelevel" %% "cats-effect" % "3.4.9", - "dev.zio" %% "zio-interop-cats" % "23.1.0.3" % "test", - "dev.zio" %% "zio-test" % zioVersion % "test", - "dev.zio" %% "zio-test-sbt" % zioVersion % "test" - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + "org.http4s" %% "http4s-dsl" % Version.http4s, + "dev.zio" %% "zio" % Version.zio, + "org.typelevel" %% "cats-effect" % Version.catsEffect, + "dev.zio" %% "zio-interop-cats" % Version.zioInteropCats % "test", + "dev.zio" %% "zio-test" % Version.zio % "test", + "dev.zio" %% "zio-test-sbt" % Version.zio % "test" + ) ) .dependsOn(zioJsonJVM) - .enablePlugins(BuildInfoPlugin) lazy val zioJsonInteropRefined = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("zio-json-interop-refined")) .dependsOn(zioJson) - .settings(stdSettings("zio-json-interop-refined")) + .settings(stdSettings(Some("zio-json-interop-refined"), turnCompilerWarningIntoErrors = false)) .settings(buildInfoSettings("zio.json.interop.refined")) .settings( + crossScalaVersions -= scala3.value, // no working version of refined for scala 3 yet, published artifacts are incomplete libraryDependencies ++= Seq( - "eu.timepit" %%% "refined" % "0.11.2", - "dev.zio" %%% "zio-test" % zioVersion % "test", - "dev.zio" %%% "zio-test-sbt" % zioVersion % "test" - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + "eu.timepit" %%% "refined" % Version.refined, + "dev.zio" %%% "zio-test" % Version.zio % "test", + "dev.zio" %%% "zio-test-sbt" % Version.zio % "test" + ) ) - .enablePlugins(BuildInfoPlugin) + .jsSettings(jsSettings) + .jvmSettings(jvmSettings) + .nativeSettings(nativeSettings) lazy val zioJsonInteropScalaz7x = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("zio-json-interop-scalaz7x")) .dependsOn(zioJson) - .settings(stdSettings("zio-json-interop-scalaz7x")) + .settings(stdSettings(Some("zio-json-interop-scalaz7x"), turnCompilerWarningIntoErrors = false)) .settings(buildInfoSettings("zio.json.interop.scalaz7x")) .settings( - crossScalaVersions -= ScalaDotty, libraryDependencies ++= Seq( - "org.scalaz" %%% "scalaz-core" % "7.3.8", - "dev.zio" %%% "zio-test" % zioVersion % "test", - "dev.zio" %%% "zio-test-sbt" % zioVersion % "test" - ), - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + "org.scalaz" %%% "scalaz-core" % Version.scalaz, + "dev.zio" %%% "zio-test" % Version.zio % "test", + "dev.zio" %%% "zio-test-sbt" % Version.zio % "test" + ) ) - .enablePlugins(BuildInfoPlugin) + .jsSettings(jsSettings) + .jvmSettings(jvmSettings) + .nativeSettings(nativeSettings) lazy val docs = project .in(file("zio-json-docs")) + .settings(macroExpansionSettings) .dependsOn( zioJsonJVM, zioJsonYaml, @@ -387,12 +342,11 @@ lazy val docs = project zioJsonInteropScalaz7x.jvm ) .settings( - crossScalaVersions -= ScalaDotty, - moduleName := "zio-json-docs", - scalacOptions += "-Ymacro-annotations", - projectName := "ZIO JSON", + crossScalaVersions -= scala3.value, + moduleName := "zio-json-docs", + projectName := "ZIO JSON", mainModuleName := (zioJsonJVM / moduleName).value, - projectStage := ProjectStage.ProductionReady, + projectStage := ProjectStage.ProductionReady, ScalaUnidoc / unidoc / unidocProjectFilter := inProjects( zioJsonJVM, zioJsonYaml, diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index 77ec4c3cf..53e63edd0 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -5,99 +5,7 @@ import sbt._ import sbtbuildinfo.BuildInfoKeys._ import sbtbuildinfo._ import sbtcrossproject.CrossPlugin.autoImport._ - object BuildHelper { - private val versions: Map[String, String] = { - import org.snakeyaml.engine.v2.api.{ Load, LoadSettings } - import java.util.{ List => JList, Map => JMap } - import scala.jdk.CollectionConverters._ - - val doc = new Load(LoadSettings.builder().build()) - .loadFromReader(scala.io.Source.fromFile(".github/workflows/ci.yml").bufferedReader()) - val yaml = doc.asInstanceOf[JMap[String, JMap[String, JMap[String, JMap[String, JMap[String, JList[String]]]]]]] - val list = yaml.get("jobs").get("test").get("strategy").get("matrix").get("scala").asScala - list.map(v => (v.split('.').take(2).mkString("."), v)).toMap - } - val Scala212: String = versions("2.12") - val Scala213: String = versions("2.13") - val ScalaDotty: String = "3.3.4" - - val SilencerVersion = "1.7.19" - - private val stdOptions = Seq( - "-deprecation", - "-encoding", - "UTF-8", - "-feature", - "-unchecked" - ) ++ { - if (sys.env.contains("CI")) { - // Seq("-Xfatal-warnings") // enable this when we are ready to enforce this - Nil - } else { - Nil // to enable Scalafix locally - } - } - - private val std2xOptions = Seq( - "-language:higherKinds", - "-language:existentials", - "-explaintypes", - "-Yrangepos", - "-Xlint:_,-missing-interpolator,-type-parameter-shadow", - "-Ywarn-numeric-widen", - "-Ywarn-value-discard" - ) - - private def optimizerOptions(optimize: Boolean) = - if (optimize) - Seq( - "-opt:l:inline", - "-opt-inline-from:zio.internal.**" - ) - else Nil - - def buildInfoSettings(packageName: String) = - Seq( - buildInfoKeys := Seq[BuildInfoKey](organization, moduleName, name, version, scalaVersion, sbtVersion, isSnapshot), - buildInfoPackage := packageName - ) - - val dottySettings = Seq( - crossScalaVersions += ScalaDotty, - scalacOptions ++= { - if (scalaVersion.value == ScalaDotty) - Seq("-noindent") - else - Seq() - }, - scalacOptions --= { - if (scalaVersion.value == ScalaDotty) - Seq("-Xfatal-warnings") - else - Seq() - }, - Compile / doc / sources := { - val old = (Compile / doc / sources).value - if (scalaVersion.value == ScalaDotty) { - Nil - } else { - old - } - }, - Test / parallelExecution := { - val old = (Test / parallelExecution).value - if (scalaVersion.value == ScalaDotty) { - false - } else { - old - } - } - ) - - val scalaReflectSettings = Seq( - libraryDependencies ++= Seq("dev.zio" %%% "izumi-reflect" % "1.0.0-M10") - ) // Keep this consistent with the version in .core-tests/shared/src/test/scala/REPLSpec.scala val replSettings = makeReplSettings { @@ -138,184 +46,9 @@ object BuildHelper { Compile / console / initialCommands := initialCommandsStr ) - def extraOptions(scalaVersion: String, optimize: Boolean) = - CrossVersion.partialVersion(scalaVersion) match { - case Some((3, 1)) => - Seq( - "-language:implicitConversions", - "-Xignore-scala2-macros" - ) - case Some((2, 13)) => - Seq( - "-Ywarn-unused:params,-implicits", - "-Wconf:msg=Boolean literals should be passed:s" - ) ++ std2xOptions ++ optimizerOptions(optimize) - case Some((2, 12)) => - Seq( - "-opt-warnings", - "-Ywarn-extra-implicit", - "-Ywarn-unused:_,imports", - "-Ywarn-unused:imports", - "-Ypartial-unification", - "-Yno-adapted-args", - "-Ywarn-inaccessible", - "-Ywarn-infer-any", - "-Ywarn-nullary-override", - "-Ywarn-nullary-unit", - "-Ywarn-unused:params,-implicits", - "-Xfuture", - "-Xsource:2.13", - "-Xmax-classfile-name", - "242" - ) ++ std2xOptions ++ optimizerOptions(optimize) - case _ => Seq.empty - } - - def platformSpecificSources(platform: String, conf: String, baseDirectory: File)(versions: String*) = for { - platform <- List("shared", platform) - version <- "scala" :: versions.toList.map("scala-" + _) - result = baseDirectory.getParentFile / platform.toLowerCase / "src" / conf / version - if result.exists - } yield result - - def crossPlatformSources(scalaVer: String, platform: String, conf: String, baseDir: File) = { - val versions = CrossVersion.partialVersion(scalaVer) match { - case Some((2, 12)) => - List("2.12", "2.12+", "2.12-2.13", "2.x") - case Some((2, 13)) => - List("2.13", "2.12+", "2.13+", "2.12-2.13", "2.x") - case Some((3, _)) => - List("dotty", "2.12+", "2.13+", "3.x") - case _ => - List.empty - } - platformSpecificSources(platform, conf, baseDir)(versions: _*) - } - - lazy val crossProjectSettings = Seq( - Compile / unmanagedSourceDirectories ++= { - crossPlatformSources( - scalaVersion.value, - crossProjectPlatform.value.identifier, - "main", - baseDirectory.value - ) - }, - Test / unmanagedSourceDirectories ++= { - crossPlatformSources( - scalaVersion.value, - crossProjectPlatform.value.identifier, - "test", - baseDirectory.value - ) - } - ) - - def stdSettings(prjName: String) = Seq( - name := s"$prjName", - crossScalaVersions := Seq(Scala212, Scala213, ScalaDotty), - ThisBuild / scalaVersion := Scala213, - scalacOptions ++= stdOptions ++ extraOptions(scalaVersion.value, optimize = !isSnapshot.value), - libraryDependencies ++= { - if (scalaVersion.value == ScalaDotty) - Seq( - "com.github.ghik" % s"silencer-lib_$Scala213" % SilencerVersion % Provided - ) - else - Seq( - "com.github.ghik" % "silencer-lib" % SilencerVersion % Provided cross CrossVersion.full, - compilerPlugin("com.github.ghik" % "silencer-plugin" % SilencerVersion cross CrossVersion.full), - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full) - ) - }, - semanticdbEnabled := scalaVersion.value != ScalaDotty, // enable SemanticDB - semanticdbOptions += "-P:semanticdb:synthetics:on", - semanticdbVersion := "4.10.2", - Test / parallelExecution := true, - incOptions ~= (_.withLogRecompileOnMacro(false)), - autoAPIMappings := true, - unusedCompileDependenciesFilter -= moduleFilter("org.scala-js", "scalajs-library") - ) - - def macroExpansionSettings = Seq( - scalacOptions ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => Seq("-Ymacro-annotations") - case _ => Seq.empty - } - }, - libraryDependencies ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, x)) if x <= 12 => - Seq(compilerPlugin(("org.scalamacros" % "paradise" % "2.1.1").cross(CrossVersion.full))) - case _ => Seq.empty - } - } - ) - - def macroDefinitionSettings = Seq( - scalacOptions += "-language:experimental.macros", - libraryDependencies ++= { - if (scalaVersion.value == ScalaDotty) Seq() - else - Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", - "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided" - ) - } - ) - - val scalaJavaTimeVersion = "2.6.0" - - def jsSettings = + def usefulTasksAndSettings: Def.Initialize[Seq[(String, String)]] = Def.setting { Seq( - libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % scalaJavaTimeVersion, - libraryDependencies += "io.github.cquiroz" %%% "scala-java-time-tzdb" % scalaJavaTimeVersion + "docs/docusaurusCreateSite" -> "Generates the ZIO microsite." ) - - def nativeSettings = Seq( - Test / skip := true, - doc / skip := true, - Compile / doc / sources := Seq.empty - ) - - val scalaReflectTestSettings: List[Setting[_]] = List( - libraryDependencies ++= { - if (scalaVersion.value == ScalaDotty) - Seq("org.scala-lang" % "scala-reflect" % Scala213 % Test) - else - Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Test) - } - ) - - def welcomeMessage = onLoadMessage := { - import scala.Console - - def header(text: String): String = s"${Console.RED}$text${Console.RESET}" - - def item(text: String): String = s"${Console.GREEN}> ${Console.CYAN}$text${Console.RESET}" - def subItem(text: String): String = s" ${Console.YELLOW}> ${Console.CYAN}$text${Console.RESET}" - - s"""|${header(" ________ ___")} - |${header("|__ /_ _/ _ \\")} - |${header(" / / | | | | |")} - |${header(" / /_ | | |_| |")} - |${header(s"/____|___\\___/ ${version.value}")} - | - |Useful sbt tasks: - |${item("build")} - Prepares sources, compiles and runs tests. - |${item("prepare")} - Prepares sources by applying scalafmt - |${item("fmt")} - Formats source files using scalafmt - |${item("~compileJVM")} - Compiles all JVM modules (file-watch enabled) - |${item("testJVM")} - Runs all JVM tests - |${item("testJS")} - Runs all ScalaJS tests - |${item("testOnly *.YourSpec -- -t \"YourLabel\"")} - Only runs tests with matching term e.g. - |${subItem("coreTestsJVM/testOnly *.ZIOSpec -- -t \"happy-path\"")} - |${item("docs/docusaurusCreateSite")} - Generates the ZIO microsite - """.stripMargin - } - - implicit class ModuleHelper(p: Project) { - def module: Project = p.in(file(p.id)).settings(stdSettings(p.id)) } } diff --git a/project/NeoJmhPlugin.scala b/project/NeoJmhPlugin.scala index a831c348b..ef9615355 100644 --- a/project/NeoJmhPlugin.scala +++ b/project/NeoJmhPlugin.scala @@ -27,8 +27,7 @@ object NeoJmhKeys { } /** - * https://github.com/ktoso/sbt-jmh/ rewritten as an idiomatic sbt - * Configuration (not requiring a separate Project). + * https://github.com/ktoso/sbt-jmh/ rewritten as an idiomatic sbt Configuration (not requiring a separate Project). */ object NeoJmhPlugin extends AutoPlugin { import NeoJmhKeys._ @@ -45,16 +44,16 @@ object NeoJmhPlugin extends AutoPlugin { override def projectConfigurations = Seq(Jmh, JmhInternal) override def buildSettings = Seq( - jmhVersion := "1.36", + jmhVersion := "1.36", jmhExtrasVersion := "0.3.7" ) override def projectSettings = inConfig(Jmh)( Defaults.testSettings ++ Seq( - run := (JmhInternal / run).evaluated, + run := (JmhInternal / run).evaluated, neoJmhGenerator := "reflection", - neoJmhYourkit := Nil, + neoJmhYourkit := Nil, javaOptions ++= Seq( "-XX:+PerfDisableSharedMem", "-XX:+AlwaysPreTouch", @@ -71,10 +70,10 @@ object NeoJmhPlugin extends AutoPlugin { ) ) ++ inConfig(JmhInternal)( Defaults.testSettings ++ Seq( - javaOptions := (Jmh / javaOptions).value, - envVars := (Jmh / envVars).value, + javaOptions := (Jmh / javaOptions).value, + envVars := (Jmh / envVars).value, run / mainClass := Some("org.openjdk.jmh.Main"), - run / fork := true, + run / fork := true, dependencyClasspath ++= (Jmh / fullClasspath).value, sourceGenerators += generateJmhSourcesAndResources.map { case (sources, _) => sources diff --git a/project/Version.scala b/project/Version.scala new file mode 100644 index 000000000..a6ad66004 --- /dev/null +++ b/project/Version.scala @@ -0,0 +1,16 @@ +object Version { + + val catsEffect = "3.4.9" + val circe = "0.14.10" + val http4s = "0.23.29" + val izumi = "1.0.0-M10" + val magnolia1_2 = "1.1.10" + val magnolia1_3 = "1.3.7" + val refined = "0.11.2" + val scalaz = "7.3.8" + val silencer = "1.7.19" + val snakeyaml = "2.3" + val scalaJavaTime = "2.6.0" + val zio = "2.1.11" + val zioInteropCats = "23.1.0.3" +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 20f449927..b3d5a3c31 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,16 +1,9 @@ -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.0") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.6.1") -addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.3.1") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.4") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0") -addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.5") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0") -addSbtPlugin("pl.project13.scala" % "sbt-jcstress" % "0.2.0") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") -addSbtPlugin("dev.zio" % "zio-sbt-website" % "0.4.0-alpha.28") -addSbtPlugin("dev.zio" % "zio-sbt-website" % "0.4.0-alpha.27") +val zioSbtVersion = "0.0.0+529-d9aba4fa-SNAPSHOT" -libraryDependencies += "org.snakeyaml" % "snakeyaml-engine" % "2.8" +addSbtPlugin("pl.project13.scala" % "sbt-jcstress" % "0.2.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") +addSbtPlugin("nl.thijsbroersen" % "zio-sbt-ci" % zioSbtVersion) +addSbtPlugin("nl.thijsbroersen" % "zio-sbt-ecosystem" % zioSbtVersion) +addSbtPlugin("nl.thijsbroersen" % "zio-sbt-website" % zioSbtVersion) + +resolvers ++= Resolver.sonatypeOssRepos("snapshots") diff --git a/zio-json-golden/src/main/scala/zio/json/golden/filehelpers.scala b/zio-json-golden/src/main/scala/zio/json/golden/filehelpers.scala index 21a57da4b..b6303877d 100644 --- a/zio-json-golden/src/main/scala/zio/json/golden/filehelpers.scala +++ b/zio-json-golden/src/main/scala/zio/json/golden/filehelpers.scala @@ -1,13 +1,11 @@ package zio.json.golden -import java.io.{ File, IOException } -import java.nio.file.{ Path } -import zio.{ test => _, _ } import zio.json._ - import zio.stacktracer.TracingImplicits.disableAutoTrace +import zio.{ test => _, _ } -import java.nio.file.Files +import java.io.{ File, IOException } +import java.nio.file.{ Files, Path } object filehelpers { diff --git a/zio-json-golden/src/main/scala/zio/json/golden/package.scala b/zio-json-golden/src/main/scala/zio/json/golden/package.scala index 8bdf6aec8..954c38690 100644 --- a/zio-json-golden/src/main/scala/zio/json/golden/package.scala +++ b/zio-json-golden/src/main/scala/zio/json/golden/package.scala @@ -1,17 +1,14 @@ package zio.json -import scala.annotation.nowarn - -import zio.Tag -import zio.{ test => _, _ } +import zio.json.ast._ import zio.json.golden.filehelpers._ import zio.stacktracer.TracingImplicits.disableAutoTrace import zio.test._ -import zio.test.diff._ import zio.test.diff.Diff._ -import java.nio.file.{ Paths, Path, Files } +import zio.test.diff._ +import zio.{ Tag, test => _, _ } -import zio.json.ast._ +import java.nio.file.{ Files, Path, Paths } package object golden { diff --git a/zio-json-interop-refined/shared/src/main/scala/zio/json/interop/refined/package.scala b/zio-json-interop-refined/shared/src/main/scala/zio/json/interop/refined/package.scala index ba558edc8..65d874923 100644 --- a/zio-json-interop-refined/shared/src/main/scala/zio/json/interop/refined/package.scala +++ b/zio-json-interop-refined/shared/src/main/scala/zio/json/interop/refined/package.scala @@ -1,7 +1,7 @@ package zio.json.interop import eu.timepit.refined.api.{ Refined, Validate } -import eu.timepit.refined.{ refineV } +import eu.timepit.refined.refineV import zio.json._ package object refined { diff --git a/zio-json-macros/shared/src/main/scala/zio/json/jsonDerive.scala b/zio-json-macros/shared/src/main/scala/zio/json/jsonDerive.scala index 8c35c9994..ed7dc1f80 100644 --- a/zio-json-macros/shared/src/main/scala/zio/json/jsonDerive.scala +++ b/zio-json-macros/shared/src/main/scala/zio/json/jsonDerive.scala @@ -74,12 +74,11 @@ private[json] final class DeriveCodecMacros(val c: blackbox.Context) { private[this] val EncoderClass = typeOf[JsonEncoder[_]].typeSymbol.asType private[this] val CodecClass = typeOf[JsonCodec[_]].typeSymbol.asType - private[this] val macroName: Tree = { + private[this] val macroName: Tree = c.prefix.tree match { case Apply(Select(New(name), _), _) => name case _ => c.abort(c.enclosingPosition, "Unexpected macro application") } - } private[this] val (codecStyle: JsonCodecStyle, codecType: JsonCodecType) = { val style: JsonCodecStyle = macroName match { diff --git a/zio-json-yaml/src/main/scala/zio/json/yaml/YamlOptions.scala b/zio-json-yaml/src/main/scala/zio/json/yaml/YamlOptions.scala index df4940b46..238316069 100644 --- a/zio-json-yaml/src/main/scala/zio/json/yaml/YamlOptions.scala +++ b/zio-json-yaml/src/main/scala/zio/json/yaml/YamlOptions.scala @@ -1,8 +1,7 @@ package zio.json.yaml -import org.yaml.snakeyaml.DumperOptions.{ FlowStyle, LineBreak, NonPrintableStyle, ScalarStyle } import org.yaml.snakeyaml.DumperOptions - +import org.yaml.snakeyaml.DumperOptions.{ FlowStyle, LineBreak, NonPrintableStyle, ScalarStyle } import zio.json.ast.Json case class YamlOptions( @@ -20,11 +19,10 @@ case class YamlOptions( ) object YamlOptions { - private val defaultLineBreak: LineBreak = { + private val defaultLineBreak: LineBreak = Set(LineBreak.MAC, LineBreak.WIN, LineBreak.UNIX) .find(_.getString == System.lineSeparator()) .getOrElse(LineBreak.UNIX) - } val default: YamlOptions = YamlOptions( () => new DumperOptions(), diff --git a/zio-json-yaml/src/main/scala/zio/json/yaml/internal/YamlValueConstruction.scala b/zio-json-yaml/src/main/scala/zio/json/yaml/internal/YamlValueConstruction.scala index af34ded1a..724f67484 100644 --- a/zio-json-yaml/src/main/scala/zio/json/yaml/internal/YamlValueConstruction.scala +++ b/zio-json-yaml/src/main/scala/zio/json/yaml/internal/YamlValueConstruction.scala @@ -1,8 +1,8 @@ package zio.json.yaml.internal +import org.yaml.snakeyaml.LoaderOptions import org.yaml.snakeyaml.constructor.SafeConstructor import org.yaml.snakeyaml.nodes.{ MappingNode, Node } -import org.yaml.snakeyaml.LoaderOptions private[yaml] final class YamlValueConstruction extends SafeConstructor(new LoaderOptions()) { def toJavaValue(node: Node): AnyRef = diff --git a/zio-json-yaml/src/main/scala/zio/json/yaml/package.scala b/zio-json-yaml/src/main/scala/zio/json/yaml/package.scala index a82339fd2..48f31c936 100644 --- a/zio-json-yaml/src/main/scala/zio/json/yaml/package.scala +++ b/zio-json-yaml/src/main/scala/zio/json/yaml/package.scala @@ -1,13 +1,13 @@ package zio.json import org.yaml.snakeyaml.DumperOptions.{ NonPrintableStyle, ScalarStyle } +import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.emitter.Emitter import org.yaml.snakeyaml.error.YAMLException import org.yaml.snakeyaml.nodes.{ ScalarNode, _ } import org.yaml.snakeyaml.reader.StreamReader import org.yaml.snakeyaml.resolver.Resolver import org.yaml.snakeyaml.serializer._ -import org.yaml.snakeyaml.Yaml import zio.Chunk import zio.json.ast.Json import zio.json.yaml.internal.YamlValueConstruction diff --git a/zio-json/jvm/src/jmh/scala/zio/json/GoogleMapsAPIBenchmarks.scala b/zio-json/jvm/src/jmh/scala/zio/json/GoogleMapsAPIBenchmarks.scala index 277455ad1..d8e994145 100644 --- a/zio-json/jvm/src/jmh/scala/zio/json/GoogleMapsAPIBenchmarks.scala +++ b/zio-json/jvm/src/jmh/scala/zio/json/GoogleMapsAPIBenchmarks.scala @@ -62,8 +62,8 @@ class GoogleMapsAPIBenchmarks { @Setup def setup(): Unit = { - //Distance Matrix API call for top-10 by population cities in US: - //https://maps.googleapis.com/maps/api/distancematrix/json?origins=New+York|Los+Angeles|Chicago|Houston|Phoenix+AZ|Philadelphia|San+Antonio|San+Diego|Dallas|San+Jose&destinations=New+York|Los+Angeles|Chicago|Houston|Phoenix+AZ|Philadelphia|San+Antonio|San+Diego|Dallas|San+Jose + // Distance Matrix API call for top-10 by population cities in US: + // https://maps.googleapis.com/maps/api/distancematrix/json?origins=New+York|Los+Angeles|Chicago|Houston|Phoenix+AZ|Philadelphia|San+Antonio|San+Diego|Dallas|San+Jose&destinations=New+York|Los+Angeles|Chicago|Houston|Phoenix+AZ|Philadelphia|San+Antonio|San+Diego|Dallas|San+Jose jsonString = getResourceAsString("google_maps_api_response.json") jsonChars = asChars(jsonString) jsonStringCompact = getResourceAsString( diff --git a/zio-json/jvm/src/jmh/scala/zio/json/SyntheticBenchmarks.scala b/zio-json/jvm/src/jmh/scala/zio/json/SyntheticBenchmarks.scala index 26e2ddaad..1f04cd89c 100644 --- a/zio-json/jvm/src/jmh/scala/zio/json/SyntheticBenchmarks.scala +++ b/zio-json/jvm/src/jmh/scala/zio/json/SyntheticBenchmarks.scala @@ -38,7 +38,7 @@ object Nested { @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(value = 1) class SyntheticBenchmarks { - //@Param(Array("100", "1000")) + // @Param(Array("100", "1000")) var size: Int = 500 var jsonString: String = _ var jsonChars: CharSequence = _ diff --git a/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala b/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala index 624f8827d..18bf3a5a0 100644 --- a/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala +++ b/zio-json/jvm/src/jmh/scala/zio/json/internal/SafeNumbersBenchmarks.scala @@ -10,7 +10,7 @@ import org.openjdk.jmh.annotations._ @Fork(value = 1) class SafeNumbersBenchInt { - //@Param(Array("100", "1000")) + // @Param(Array("100", "1000")) var size: Int = 10000 // invalid input. e.g. out of range longs @@ -69,7 +69,7 @@ class SafeNumbersBenchInt { @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(value = 1) class SafeNumbersBenchFloat { - //@Param(Array("100", "1000")) + // @Param(Array("100", "1000")) var size: Int = 10000 var invalids: Array[String] = _ @@ -135,7 +135,7 @@ class SafeNumbersBenchFloat { @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(value = 1) class SafeNumbersBenchBigDecimal { - //@Param(Array("100", "1000")) + // @Param(Array("100", "1000")) var size: Int = 10000 var invalids: Array[String] = _ diff --git a/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala b/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala index 11da0dbfd..7e442f983 100644 --- a/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala +++ b/zio-json/jvm/src/main/scala/zio/json/JsonDecoderPlatformSpecific.scala @@ -20,12 +20,13 @@ trait JsonDecoderPlatformSpecific[A] { self: JsonDecoder[A] => } /** - * Attempts to decode a stream of bytes using the user supplied Charset into a single value of type `A`, but may fail with - * a human-readable exception if the stream does not encode a value of this type. + * Attempts to decode a stream of bytes using the user supplied Charset into a single value of type `A`, but may fail + * with a human-readable exception if the stream does not encode a value of this type. * * Note: This method may not consume the full string. * - * @see [[decodeJsonStream]] For a `Char` stream variant + * @see + * [[decodeJsonStream]] For a `Char` stream variant */ final def decodeJsonStreamInput[R]( stream: ZStream[R, Throwable, Byte], @@ -41,12 +42,13 @@ trait JsonDecoderPlatformSpecific[A] { self: JsonDecoder[A] => } /** - * Attempts to decode a stream of characters into a single value of type `A`, but may fail with - * a human-readable exception if the stream does not encode a value of this type. + * Attempts to decode a stream of characters into a single value of type `A`, but may fail with a human-readable + * exception if the stream does not encode a value of this type. * * Note: This method may not consume the full string. * - * @see also [[decodeJsonStreamInput]] + * @see + * also [[decodeJsonStreamInput]] */ final def decodeJsonStream[R](stream: ZStream[R, Throwable, Char]): ZIO[R, Throwable, A] = ZIO.scoped[R](stream.toReader.flatMap(readAll)) diff --git a/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala b/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala index eab496ca8..5966849fc 100644 --- a/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala +++ b/zio-json/jvm/src/test/scala-2/zio/json/DecoderPlatformSpecificSpec.scala @@ -18,7 +18,7 @@ import java.nio.file.Paths object DecoderPlatformSpecificSpec extends ZIOSpecDefault { - val spec = + val spec: Spec[Annotations with Live with Sized with TestConfig, Throwable] = suite("Decoder")( test("excessively nested structures") { // JVM specific: getResourceAsString not yet supported @@ -236,7 +236,7 @@ object DecoderPlatformSpecificSpec extends ZIOSpecDefault { import logEvent._ for { - lines <- readJsonLinesAs[Event](Paths.get("zio-json/jvm/src/test/resources/log.jsonlines")).runCollect + lines <- readJsonLinesAs[Event](Paths.get("src/test/resources/log.jsonlines")).runCollect } yield { assert(lines(0))(equalTo(Event(1603669875, "hello"))) && assert(lines(1))(equalTo(Event(1603669876, "world"))) @@ -273,7 +273,7 @@ object DecoderPlatformSpecificSpec extends ZIOSpecDefault { ) ) - def testAst(label: String) = + def testAst(label: String): Spec[Any, Throwable] = test(label) { getResourceAsStringM(s"jawn/$label.json").flatMap { input => val expected = jawn.JParser.parseFromString(input).toEither.map(fromJawn) diff --git a/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala b/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala index 7479e378b..59e0cd6e8 100644 --- a/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala +++ b/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala @@ -9,7 +9,7 @@ import zio.Chunk import zio.json.ast.Json import zio.stream.{ ZSink, ZStream } import zio.test.Assertion._ -import zio.test.{ ZIOSpecDefault, assert, _ } +import zio.test.{ Spec, ZIOSpecDefault, assert, _ } import java.io.IOException import java.nio.file.Files @@ -17,7 +17,7 @@ import java.nio.file.Files object EncoderPlatformSpecificSpec extends ZIOSpecDefault { import testzio.json.DecoderSpec.logEvent._ - val spec = + val spec: Spec[Any, Throwable] = suite("Encoder")( suite("roundtrip")( testRoundTrip[DistanceMatrix]("google_maps_api_response"), diff --git a/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala b/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala index e96cfdae7..498fc9cc2 100644 --- a/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala +++ b/zio-json/jvm/src/test/scala/zio/json/TestUtils.scala @@ -15,9 +15,9 @@ object TestUtils { def getResourceAsString(res: String): String = { val is = getClass.getClassLoader.getResourceAsStream(res) try { - val baos = new java.io.ByteArrayOutputStream() - val data = Array.ofDim[Byte](2048) - var len: Int = 0 + val baos = new java.io.ByteArrayOutputStream() + val data = Array.ofDim[Byte](2048) + var len: Int = 0 def read(): Int = { len = is.read(data); len } while (read() != -1) baos.write(data, 0, len) diff --git a/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala b/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala index 41f184676..afa680ba8 100644 --- a/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala +++ b/zio-json/jvm/src/test/scala/zio/json/internal/SafeNumbersSpec.scala @@ -1,12 +1,13 @@ package testzio.json.internal import testzio.json.Gens._ +import zio.Scope import zio.json.internal._ import zio.test.Assertion._ import zio.test._ object SafeNumbersSpec extends ZIOSpecDefault { - val spec = + val spec: Spec[Environment with TestEnvironment with Scope, Any] = suite("SafeNumbers")( test("valid big decimals") { check(genBigDecimal)(i => assert(SafeNumbers.bigDecimal(i.toString, 2048))(isSome(equalTo(i)))) diff --git a/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala b/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala index 8ae865a76..1b57d100d 100644 --- a/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala +++ b/zio-json/jvm/src/test/scala/zio/json/internal/StringMatrixSpec.scala @@ -110,16 +110,16 @@ object StringMatrixSpec extends ZIOSpecDefault { } ) - val genNonEmptyString = + val genNonEmptyString: Gen[Any, String] = Gen.alphaNumericString.filter(_.nonEmpty) - val genTestStrings = + val genTestStrings: Gen[Any, List[String]] = for { n <- Gen.int(1, 63) xs <- Gen.setOfN(n)(genNonEmptyString) } yield xs.toList - val genTestStringsAndAliases = + val genTestStringsAndAliases: Gen[Any, (List[String], List[(String, Int)])] = for { xsn <- Gen.int(1, 63) xs <- Gen.setOfN(xsn)(genNonEmptyString) diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala index 42fffbb92..41ce81556 100644 --- a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala @@ -12,8 +12,7 @@ import scala.annotation._ import scala.language.experimental.macros /** - * If used on a case class field, determines the name of the JSON field. - * Defaults to the case class field name. + * If used on a case class field, determines the name of the JSON field. Defaults to the case class field name. */ final case class jsonField(name: String) extends Annotation @@ -25,17 +24,14 @@ final case class jsonAliases(alias: String, aliases: String*) extends Annotation final class jsonExplicitNull extends Annotation /** - * If used on a sealed class, will determine the name of the field for - * disambiguating classes. + * If used on a sealed class, will determine the name of the field for disambiguating classes. * - * The default is to not use a typehint field and instead - * have an object with a single key that is the class name. + * The default is to not use a typehint field and instead have an object with a single key that is the class name. * - * Note that using a discriminator is less performant, uses more memory, and may - * be prone to DOS attacks that are impossible with the default encoding. In - * addition, there is slightly less type safety when using custom product - * encoders (which must write an unenforced object type). Only use this option - * if you must model an externally defined schema. + * Note that using a discriminator is less performant, uses more memory, and may be prone to DOS attacks that are + * impossible with the default encoding. In addition, there is slightly less type safety when using custom product + * encoders (which must write an unenforced object type). Only use this option if you must model an externally defined + * schema. */ final case class jsonDiscriminator(name: String) extends Annotation // TODO a strategy where the constructor is inferred from the field names, only @@ -80,16 +76,14 @@ object ziojson_03 { } /** - * If used on a case class, determines the strategy of member names - * transformation during serialization and deserialization. Four common - * strategies are provided above and a custom one to support specific use cases. + * If used on a case class, determines the strategy of member names transformation during serialization and + * deserialization. Four common strategies are provided above and a custom one to support specific use cases. */ final case class jsonMemberNames(format: JsonMemberFormat) extends Annotation private[json] object jsonMemberNames { /** - * ~~Stolen~~ Borrowed from jsoniter-scala by Andriy Plokhotnyuk - * (he even granted permission for this, imagine that!) + * ~~Stolen~~ Borrowed from jsoniter-scala by Andriy Plokhotnyuk (he even granted permission for this, imagine that!) */ import java.lang.Character._ @@ -172,27 +166,26 @@ private[json] object jsonMemberNames { } /** - * If used on a case class will determine the type hint value for disambiguating - * sealed traits. Defaults to the short type name. + * If used on a case class will determine the type hint value for disambiguating sealed traits. Defaults to the short + * type name. */ final case class jsonHint(name: String) extends Annotation /** - * If used on a sealed class will determine the strategy of type hint value transformation for disambiguating - * classes during serialization and deserialization. Same strategies are provided as for [[jsonMemberNames]]. + * If used on a sealed class will determine the strategy of type hint value transformation for disambiguating classes + * during serialization and deserialization. Same strategies are provided as for [[jsonMemberNames]]. */ final case class jsonHintNames(format: JsonMemberFormat) extends Annotation /** - * If used on a case class, will exit early if any fields are in the JSON that - * do not correspond to field names in the case class. + * If used on a case class, will exit early if any fields are in the JSON that do not correspond to field names in the + * case class. * - * This adds extra protections against a DOS attacks but means that changes in - * the schema will result in a hard error rather than silently ignoring those - * fields. + * This adds extra protections against a DOS attacks but means that changes in the schema will result in a hard error + * rather than silently ignoring those fields. * - * Cannot be combined with `@jsonDiscriminator` since it is considered an extra - * field from the perspective of the case class. + * Cannot be combined with `@jsonDiscriminator` since it is considered an extra field from the perspective of the case + * class. */ final class jsonNoExtraFields extends Annotation @@ -205,10 +198,14 @@ final class jsonExclude extends Annotation /** * Implicit codec derivation configuration. * - * @param sumTypeHandling see [[jsonDiscriminator]] - * @param fieldNameMapping see [[jsonMemberNames]] - * @param allowExtraFields see [[jsonNoExtraFields]] - * @param sumTypeMapping see [[jsonHintNames]] + * @param sumTypeHandling + * see [[jsonDiscriminator]] + * @param fieldNameMapping + * see [[jsonMemberNames]] + * @param allowExtraFields + * see [[jsonNoExtraFields]] + * @param sumTypeMapping + * see [[jsonHintNames]] */ final case class JsonCodecConfiguration( sumTypeHandling: SumTypeHandling = WrapperWithClassNameField, @@ -235,18 +232,15 @@ object JsonCodecConfiguration { } /** - * For sealed classes, will determine the name of the field for - * disambiguating classes. + * For sealed classes, will determine the name of the field for disambiguating classes. * - * The default is to not use a typehint field and instead - * have an object with a single key that is the class name. + * The default is to not use a typehint field and instead have an object with a single key that is the class name. * See [[WrapperWithClassNameField]]. * - * Note that using a discriminator is less performant, uses more memory, and may - * be prone to DOS attacks that are impossible with the default encoding. In - * addition, there is slightly less type safety when using custom product - * encoders (which must write an unenforced object type). Only use this option - * if you must model an externally defined schema. + * Note that using a discriminator is less performant, uses more memory, and may be prone to DOS attacks that are + * impossible with the default encoding. In addition, there is slightly less type safety when using custom product + * encoders (which must write an unenforced object type). Only use this option if you must model an externally + * defined schema. */ final case class DiscriminatorField(name: String) extends SumTypeHandling { override def discriminatorField: Option[String] = Some(name) diff --git a/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala b/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala index ad020b005..6f87ed6e5 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala @@ -11,10 +11,8 @@ trait DecoderLowPriorityVersionSpecific { inline given unionOfStringEnumeration[T](using IsUnionOf[String, T]): JsonDecoder[T] = val values = UnionDerivation.constValueUnionTuple[String, T] - JsonDecoder.string.mapOrFail( - { - case raw if values.toList.contains(raw) => Right(raw.asInstanceOf[T]) - case _ => Left("expected one of: " + values.toList.mkString(", ")) - } - ) + JsonDecoder.string.mapOrFail { + case raw if values.toList.contains(raw) => Right(raw.asInstanceOf[T]) + case _ => Left("expected one of: " + values.toList.mkString(", ")) + } } diff --git a/zio-json/shared/src/main/scala-3/zio/json/union_derivation.scala b/zio-json/shared/src/main/scala-3/zio/json/union_derivation.scala index c260f472c..e733473eb 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/union_derivation.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/union_derivation.scala @@ -15,7 +15,7 @@ private[json] object IsUnionOf: private def deriveImpl[T, A](using quotes: Quotes, t: Type[T], a: Type[A]): Expr[IsUnionOf[T, A]] = import quotes.reflect.* - val tpe: TypeRepr = TypeRepr.of[A] + val tpe: TypeRepr = TypeRepr.of[A] val bound: TypeRepr = TypeRepr.of[T] def validateTypes(tpe: TypeRepr): Unit = diff --git a/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala b/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala index 32e3c53ec..25d368f73 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala @@ -20,20 +20,17 @@ import zio.{ Chunk, NonEmptyChunk } import scala.collection.immutable /** - * A `JsonCodec[A]` instance has the ability to encode values of type `A` into JSON, together with - * the ability to decode such JSON into values of type `A`. + * A `JsonCodec[A]` instance has the ability to encode values of type `A` into JSON, together with the ability to decode + * such JSON into values of type `A`. * - * Instances of this trait should satisfy round-tripping laws: that is, for every value, instances - * must be able to successfully encode the value into JSON, and then successfully decode the same - * value from such JSON. + * Instances of this trait should satisfy round-tripping laws: that is, for every value, instances must be able to + * successfully encode the value into JSON, and then successfully decode the same value from such JSON. * * For more information, see [[JsonDecoder]] and [[JsonEncoder]]. * - * {{ - * val intCodec: JsonCodec[Int] = JsonCodec[Int] + * {{ val intCodec: JsonCodec[Int] = JsonCodec[Int] * - * intCodec.encodeJson(intCodec.encodeJson(42)) == Right(42) - * }} + * intCodec.encodeJson(intCodec.encodeJson(42)) == Right(42) }} */ final case class JsonCodec[A](encoder: JsonEncoder[A], decoder: JsonDecoder[A]) { self => diff --git a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala index 5be8a54ec..7e24886c3 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala @@ -28,8 +28,8 @@ import scala.collection.{ immutable, mutable } import scala.util.control.NoStackTrace /** - * A `JsonDecoder[A]` instance has the ability to decode JSON to values of type `A`, potentially - * failing with an error if the JSON content does not encode a value of the given type. + * A `JsonDecoder[A]` instance has the ability to decode JSON to values of type `A`, potentially failing with an error + * if the JSON content does not encode a value of the given type. */ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { self => @@ -60,8 +60,8 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { final def <*[B](that: => JsonDecoder[B]): JsonDecoder[A] = self.zipLeft(that) /** - * Attempts to decode a value of type `A` from the specified `CharSequence`, but may fail with - * a human-readable error message if the provided text does not encode a value of this type. + * Attempts to decode a value of type `A` from the specified `CharSequence`, but may fail with a human-readable error + * message if the provided text does not encode a value of this type. * * Note: This method may not entirely consume the specified character sequence. */ @@ -79,13 +79,11 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { final def widen[B >: A]: JsonDecoder[B] = self.asInstanceOf[JsonDecoder[B]] /** - * Returns a new codec that combines this codec and the specified codec using fallback semantics: - * such that if this codec fails, the specified codec will be tried instead. - * This method may be unsafe from a security perspective: it can use more memory than hand coded - * alternative and so lead to DOS. + * Returns a new codec that combines this codec and the specified codec using fallback semantics: such that if this + * codec fails, the specified codec will be tried instead. This method may be unsafe from a security perspective: it + * can use more memory than hand coded alternative and so lead to DOS. * - * For example, in the case of an alternative between `Int` and `Boolean`, a hand coded - * alternative would look like: + * For example, in the case of an alternative between `Int` and `Boolean`, a hand coded alternative would look like: * * ``` * val decoder: JsonDecoder[AnyVal] = JsonDecoder.peekChar[AnyVal] { @@ -127,8 +125,8 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { } /** - * Returns a new codec that combines this codec and the specified codec using fallback semantics: - * such that if this codec fails, the specified codec will be tried instead. + * Returns a new codec that combines this codec and the specified codec using fallback semantics: such that if this + * codec fails, the specified codec will be tried instead. */ final def orElseEither[B](that: => JsonDecoder[B]): JsonDecoder[Either[A, B]] = self.map(Left(_)).orElse(that.map(Right(_))) @@ -151,8 +149,8 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { } /** - * Returns a new codec whose decoded values will be mapped by the specified function, which may - * itself decide to fail with some type of error. + * Returns a new codec whose decoded values will be mapped by the specified function, which may itself decide to fail + * with some type of error. */ final def mapOrFail[B](f: A => Either[String, B]): JsonDecoder[B] = new JsonDecoder[B] { @@ -180,8 +178,8 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { } /** - * Returns a new codec that combines this codec and the specified codec into a single codec that - * decodes a tuple of the values decoded by the respective codecs. + * Returns a new codec that combines this codec and the specified codec into a single codec that decodes a tuple of + * the values decoded by the respective codecs. */ final def zip[B](that: => JsonDecoder[B]): JsonDecoder[(A, B)] = JsonDecoder.tuple2(this, that) @@ -205,8 +203,8 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { throw JsonDecoder.UnsafeJson(JsonError.Message("missing") :: trace) /** - * Low-level, unsafe method to decode a value or throw an exception. This method should not be - * called in application code, although it can be implemented for user-defined data structures. + * Low-level, unsafe method to decode a value or throw an exception. This method should not be called in application + * code, although it can be implemented for user-defined data structures. */ def unsafeDecode(trace: List[JsonError], in: RetractReader): A @@ -216,8 +214,8 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { /** * Decode a value from an already parsed Json AST. * - * The default implementation encodes the Json to a byte stream and uses decode to parse that. - * Override to provide a more performant implementation. + * The default implementation encodes the Json to a byte stream and uses decode to parse that. Override to provide a + * more performant implementation. */ final def fromJsonAST(json: Json): Either[String, A] = try Right(unsafeFromJsonAST(Nil, json)) @@ -236,10 +234,9 @@ object JsonDecoder extends GeneratedTupleDecoders with DecoderLowPriority1 with def apply[A](implicit a: JsonDecoder[A]): JsonDecoder[A] = a /** - * Design note: we could require the position in the stream here to improve - * debugging messages. But the cost would be that the RetractReader would need - * to keep track and any wrappers would need to preserve the position. It may - * still be desirable to do this but at the moment it is not necessary. + * Design note: we could require the position in the stream here to improve debugging messages. But the cost would be + * that the RetractReader would need to keep track and any wrappers would need to preserve the position. It may still + * be desirable to do this but at the moment it is not necessary. */ final case class UnsafeJson(trace: List[JsonError]) extends Exception("If you see this, a developer made a mistake using JsonDecoder") diff --git a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala index 7098a1b83..d49b178a4 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala @@ -29,8 +29,8 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { self => /** - * Returns a new encoder, with a new input type, which can be transformed to the old input type - * by the specified user-defined function. + * Returns a new encoder, with a new input type, which can be transformed to the old input type by the specified + * user-defined function. */ final def contramap[B](f: B => A): JsonEncoder[B] = new JsonEncoder[B] { @@ -44,25 +44,22 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { } /** - * Returns a new encoder that can accepts an `Either[A, B]` to either, and uses either this - * encoder or the specified encoder to encode the two different types of values. + * Returns a new encoder that can accepts an `Either[A, B]` to either, and uses either this encoder or the specified + * encoder to encode the two different types of values. */ final def either[B](that: => JsonEncoder[B]): JsonEncoder[Either[A, B]] = JsonEncoder.either[A, B](self, that) /** - * Returns a new encoder that can accepts an `Either[A, B]` to either, and uses either this - * encoder or the specified encoder to encode the two different types of values. - * The difference with the classic `either` encoder is that the resulting JSON has no field - * `Left` or `Right`. - * What should be: `{"Right": "John Doe"}` is encoded as `"John Doe"` + * Returns a new encoder that can accepts an `Either[A, B]` to either, and uses either this encoder or the specified + * encoder to encode the two different types of values. The difference with the classic `either` encoder is that the + * resulting JSON has no field `Left` or `Right`. What should be: `{"Right": "John Doe"}` is encoded as `"John Doe"` */ final def orElseEither[B](that: => JsonEncoder[B]): JsonEncoder[Either[A, B]] = JsonEncoder.orElseEither[A, B](self, that) /** - * Returns a new encoder with a new input type, which can be transformed to either the input - * type of this encoder, or the input type of the specified encoder, using the user-defined - * transformation function. + * Returns a new encoder with a new input type, which can be transformed to either the input type of this encoder, or + * the input type of the specified encoder, using the user-defined transformation function. */ final def eitherWith[B, C](that: => JsonEncoder[B])(f: C => Either[A, B]): JsonEncoder[C] = self.either(that).contramap(f) @@ -77,8 +74,7 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { } /** - * This default may be overridden when this value may be missing within a JSON object and still - * be encoded. + * This default may be overridden when this value may be missing within a JSON object and still be encoded. */ def isNothing(a: A): Boolean = false @@ -92,22 +88,20 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { /** * Converts a value to a Json AST * - * The default implementation encodes the value to a Json byte stream and - * uses decode to parse that back to an AST. Override to provide a more performant - * implementation. + * The default implementation encodes the value to a Json byte stream and uses decode to parse that back to an AST. + * Override to provide a more performant implementation. */ def toJsonAST(a: A): Either[String, Json] = Json.decoder.decodeJson(encodeJson(a, None)) /** - * Returns a new encoder that is capable of encoding a tuple containing the values of this - * encoder and the specified encoder. + * Returns a new encoder that is capable of encoding a tuple containing the values of this encoder and the specified + * encoder. */ final def zip[B](that: => JsonEncoder[B]): JsonEncoder[(A, B)] = JsonEncoder.tuple2(self, that) /** - * Returns a new encoder that is capable of encoding a user-defined value, which is create from - * a tuple of the values of this encoder and the specified encoder, from the specified user- - * defined function. + * Returns a new encoder that is capable of encoding a user-defined value, which is create from a tuple of the values + * of this encoder and the specified encoder, from the specified user- defined function. */ final def zipWith[B, C](that: => JsonEncoder[B])(f: C => (A, B)): JsonEncoder[C] = self.zip(that).contramap(f) } diff --git a/zio-json/shared/src/main/scala/zio/json/JsonError.scala b/zio-json/shared/src/main/scala/zio/json/JsonError.scala index b9525ad06..f82ded949 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonError.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonError.scala @@ -16,8 +16,8 @@ package zio.json /** - * A `JsonError` value describes the ways in which decoding could fail. This structure is used - * to facilitate human-readable error messages during decoding failures. + * A `JsonError` value describes the ways in which decoding could fail. This structure is used to facilitate + * human-readable error messages during decoding failures. */ sealed abstract class JsonError diff --git a/zio-json/shared/src/main/scala/zio/json/ast/ast.scala b/zio-json/shared/src/main/scala/zio/json/ast/ast.scala index 694d95ae1..a1f44f21a 100644 --- a/zio-json/shared/src/main/scala/zio/json/ast/ast.scala +++ b/zio-json/shared/src/main/scala/zio/json/ast/ast.scala @@ -24,19 +24,15 @@ import zio.json.internal._ import scala.annotation._ /** - * This AST of JSON is made available so that arbitrary JSON may be included as - * part of a business object, it is not used as an intermediate representation, - * unlike most other JSON libraries. It is not advised to `.map` or `.mapOrFail` + * This AST of JSON is made available so that arbitrary JSON may be included as part of a business object, it is not + * used as an intermediate representation, unlike most other JSON libraries. It is not advised to `.map` or `.mapOrFail` * from these decoders, since a higher performance decoder is often available. * - * Beware of the potential for DOS attacks, since an attacker can provide much - * more data than is perhaps needed. + * Beware of the potential for DOS attacks, since an attacker can provide much more data than is perhaps needed. * - * Also beware of converting `Num` (a `BigDecimal`) into any other kind of - * number, since many of the stdlib functions are non-total or are known DOS - * vectors (e.g. calling `.toBigInteger` on a "1e214748364" will consume an - * excessive amount of heap memory). - * JsonValue / Json / JValue + * Also beware of converting `Num` (a `BigDecimal`) into any other kind of number, since many of the stdlib functions + * are non-total or are known DOS vectors (e.g. calling `.toBigInteger` on a "1e214748364" will consume an excessive + * amount of heap memory). JsonValue / Json / JValue */ sealed abstract class Json { self => final def as[A](implicit decoder: JsonDecoder[A]): Either[String, A] = decoder.fromJsonAST(self) @@ -70,8 +66,10 @@ sealed abstract class Json { self => /** * Deletes json node specified by given cursor - * @param cursor Cursor which specifies node to delete - * @return Json without specified node if node specified by cursor exists, error otherwise + * @param cursor + * Cursor which specifies node to delete + * @return + * Json without specified node if node specified by cursor exists, error otherwise */ final def delete(cursor: JsonCursor[_, _]): Either[String, Json] = { val c = cursor.asInstanceOf[JsonCursor[_, Json]] @@ -189,10 +187,11 @@ sealed abstract class Json { self => } /** - * Intersects JSON values. If both values are `Obj` or `Arr` method returns intersections of its fields/elements, otherwise - * it returns error + * Intersects JSON values. If both values are `Obj` or `Arr` method returns intersections of its fields/elements, + * otherwise it returns error * @param that - * @return Intersected json if type are compatible, error otherwise + * @return + * Intersected json if type are compatible, error otherwise */ final def intersect(that: Json): Either[String, Json] = (self, that) match { @@ -204,12 +203,12 @@ sealed abstract class Json { self => } /** - * - merging objects results in a new objects with all pairs of both sides, with the right hand - * side being used on key conflicts + * - merging objects results in a new objects with all pairs of both sides, with the right hand side being used on + * key conflicts * - * - merging arrays results in all of the individual elements being merged + * - merging arrays results in all of the individual elements being merged * - * - scalar values will be replaced by the right hand side + * - scalar values will be replaced by the right hand side */ final def merge(that: Json): Json = (self, that) match { @@ -221,10 +220,14 @@ sealed abstract class Json { self => /** * Relocates Json node from location specified by `from` cursor to location specified by `to` cursor. * - * @param from Cursor which specifies node to relocate - * @return Json without specified node if node specified by cursor exists, error otherwise - * @param to Cursor which specifies location where to relocate node - * @return Json with relocated node if node specified by cursors exist, error otherwise + * @param from + * Cursor which specifies node to relocate + * @return + * Json without specified node if node specified by cursor exists, error otherwise + * @param to + * Cursor which specifies location where to relocate node + * @return + * Json with relocated node if node specified by cursors exist, error otherwise */ final def relocate(from: JsonCursor[_, _], to: JsonCursor[_, _]): Either[String, Json] = { val f = from.asInstanceOf[JsonCursor[_, Json]] @@ -234,10 +237,14 @@ sealed abstract class Json { self => /** * Transforms json node specified by given cursor - * @param cursor Cursor which specifies node to transform - * @param f Function used to transform node - * @tparam A refined node type - * @return Json with transformed node if node specified by cursor exists, error otherwise + * @param cursor + * Cursor which specifies node to transform + * @param f + * Function used to transform node + * @tparam A + * refined node type + * @return + * Json with transformed node if node specified by cursor exists, error otherwise */ final def transformAt[A <: Json](cursor: JsonCursor[_, A])(f: A => Json): Either[String, Json] = transformOrDelete(cursor, delete = false)(x => Right(f(x))) diff --git a/zio-json/shared/src/main/scala/zio/json/codegen/Generator.scala b/zio-json/shared/src/main/scala/zio/json/codegen/Generator.scala index 523dd35b1..b787b61ab 100644 --- a/zio-json/shared/src/main/scala/zio/json/codegen/Generator.scala +++ b/zio-json/shared/src/main/scala/zio/json/codegen/Generator.scala @@ -6,8 +6,8 @@ import zio.json.ast.Json import zio.json.codegen.Generator.pascalFormat import zio.json.codegen.JsonType._ -import java.time.{ LocalDate, LocalDateTime } import java.time.format.DateTimeFormatter +import java.time.{ LocalDate, LocalDateTime } import java.util.UUID import scala.collection.immutable.ListMap import scala.math.BigDecimal.javaBigDecimal2bigDecimal @@ -16,8 +16,7 @@ import scala.util.Try object Generator { /** - * Renders the JSON string as a series of Scala case classes derived from the - * structure of the JSON. + * Renders the JSON string as a series of Scala case classes derived from the structure of the JSON. * * For example, the following JSON: * diff --git a/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala b/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala index 39dc7fd2a..35ef7dbe8 100644 --- a/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala +++ b/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala @@ -413,7 +413,7 @@ final class StringMatrix(val xs: Array[String], aliases: Array[(String, Int)] = require(aliases.forall(_._1.nonEmpty)) require(aliases.forall(p => p._2 >= 0 && p._2 < xs.length)) - val width = xs.length + aliases.length + val width: Int = xs.length + aliases.length val height: Int = xs.map(_.length).max max (if (aliases.isEmpty) 0 else aliases.map(_._1.length).max) val lengths: Array[Int] = xs.map(_.length) ++ aliases.map(_._1.length) val initial: Long = (0 until width).foldLeft(0L)((bs, r) => bs | (1L << r)) diff --git a/zio-json/shared/src/main/scala/zio/json/internal/numbers.scala b/zio-json/shared/src/main/scala/zio/json/internal/numbers.scala index c869f113e..038bdc7a9 100644 --- a/zio-json/shared/src/main/scala/zio/json/internal/numbers.scala +++ b/zio-json/shared/src/main/scala/zio/json/internal/numbers.scala @@ -21,28 +21,22 @@ import scala.util.control.NoStackTrace /** * Total, fast, number parsing. * - * The Java and Scala standard libraries throw exceptions when we attempt to - * parse an invalid number. Unfortunately, exceptions are very expensive, and - * untrusted data can be maliciously constructed to DOS a server. + * The Java and Scala standard libraries throw exceptions when we attempt to parse an invalid number. Unfortunately, + * exceptions are very expensive, and untrusted data can be maliciously constructed to DOS a server. * - * This suite of functions mitigates against such attacks by building up the - * numbers one character at a time, which has been shown through extensive - * benchmarking to be orders of magnitude faster than exception-throwing stdlib - * parsers, for valid and invalid inputs. This approach, proposed by alexknvl, - * was also benchmarked against regexp-based pre-validation. + * This suite of functions mitigates against such attacks by building up the numbers one character at a time, which has + * been shown through extensive benchmarking to be orders of magnitude faster than exception-throwing stdlib parsers, + * for valid and invalid inputs. This approach, proposed by alexknvl, was also benchmarked against regexp-based + * pre-validation. * - * Note that although the behaviour is identical to the Java stdlib when given - * the canonical form of a primitive (i.e. the .toString) of a number there may - * be differences in behaviour for non-canonical forms. e.g. the Java stdlib - * may reject "1.0" when parsed as an `BigInteger` but we may parse it as a - * `1`, although "1.1" would be rejected. Parsing of `BigDecimal` preserves the - * trailing zeros on the right but not on the left, e.g. "000.00001000" will be - * "1.000e-5", which is useful in cases where the trailing zeros denote - * measurement accuracy. + * Note that although the behaviour is identical to the Java stdlib when given the canonical form of a primitive (i.e. + * the .toString) of a number there may be differences in behaviour for non-canonical forms. e.g. the Java stdlib may + * reject "1.0" when parsed as an `BigInteger` but we may parse it as a `1`, although "1.1" would be rejected. Parsing + * of `BigDecimal` preserves the trailing zeros on the right but not on the left, e.g. "000.00001000" will be + * "1.000e-5", which is useful in cases where the trailing zeros denote measurement accuracy. * - * `BigInteger`, `BigDecimal`, `Float` and `Double` have a configurable bit - * limit on the size of the significand, to avoid OOM style attacks, which is - * 128 bits by default. + * `BigInteger`, `BigDecimal`, `Float` and `Double` have a configurable bit limit on the size of the significand, to + * avoid OOM style attacks, which is 128 bits by default. * * Results are contained in a specialisation of Option that avoids boxing. */ @@ -100,93 +94,95 @@ object SafeNumbers { if (x != x) """"NaN"""" else if (bits < 0) """"-Infinity"""" else """"Infinity"""" - } else { - val s = new java.lang.StringBuilder(24) - if (bits < 0) s.append('-') - if (x == 0.0f) s.append('0').append('.').append('0') - else { - var e = ieeeExponent - 1075 - var m = ieeeMantissa | 0x10000000000000L - var dv = 0L - var exp = 0 - if (e == 0) dv = m - else if (e >= -52 && e < 0 && m << e == 0) dv = m >> -e + } else + { + val s = new java.lang.StringBuilder(24) + if (bits < 0) s.append('-') + if (x == 0.0f) s.append('0').append('.').append('0') else { - var expShift, expCorr = 0 - var cblShift = 2 - if (ieeeExponent == 0) { - e = -1074 - m = ieeeMantissa - if (ieeeMantissa < 3) { - m *= 10 - expShift = 1 + var e = ieeeExponent - 1075 + var m = ieeeMantissa | 0x10000000000000L + var dv = 0L + var exp = 0 + if (e == 0) dv = m + else if (e >= -52 && e < 0 && m << e == 0) dv = m >> -e + else { + var expShift, expCorr = 0 + var cblShift = 2 + if (ieeeExponent == 0) { + e = -1074 + m = ieeeMantissa + if (ieeeMantissa < 3) { + m *= 10 + expShift = 1 + } + } else if (ieeeMantissa == 0 && ieeeExponent > 1) { + expCorr = 131007 + cblShift = 1 } - } else if (ieeeMantissa == 0 && ieeeExponent > 1) { - expCorr = 131007 - cblShift = 1 - } - exp = e * 315653 - expCorr >> 20 - val i = exp + 324 << 1 - val g1 = gs(i) - val g0 = gs(i + 1) - val h = (-exp * 108853 >> 15) + e + 2 - val cb = m << 2 - val outm1 = (m.toInt & 0x1) - 1 - val vb = rop(g1, g0, cb << h) - val vbls = rop(g1, g0, cb - cblShift << h) + outm1 - val vbrd = outm1 - rop(g1, g0, cb + 2 << h) - val s = vb >> 2 - if ( - s < 100 || { - dv = s / 10 // FIXME: Use Math.multiplyHigh(s, 1844674407370955168L) instead after dropping JDK 8 support - val sp40 = dv * 40 - val upin = (vbls - sp40).toInt - (((sp40 + vbrd).toInt + 40) ^ upin) >= 0 || { - dv += ~upin >>> 31 - exp += 1 - false + exp = e * 315653 - expCorr >> 20 + val i = exp + 324 << 1 + val g1 = gs(i) + val g0 = gs(i + 1) + val h = (-exp * 108853 >> 15) + e + 2 + val cb = m << 2 + val outm1 = (m.toInt & 0x1) - 1 + val vb = rop(g1, g0, cb << h) + val vbls = rop(g1, g0, cb - cblShift << h) + outm1 + val vbrd = outm1 - rop(g1, g0, cb + 2 << h) + val s = vb >> 2 + if ( + s < 100 || { + dv = + s / 10 // FIXME: Use Math.multiplyHigh(s, 1844674407370955168L) instead after dropping JDK 8 support + val sp40 = dv * 40 + val upin = (vbls - sp40).toInt + (((sp40 + vbrd).toInt + 40) ^ upin) >= 0 || { + dv += ~upin >>> 31 + exp += 1 + false + } } + ) { + val s4 = s << 2 + val uin = (vbls - s4).toInt + dv = (~ { + if ((((s4 + vbrd).toInt + 4) ^ uin) < 0) uin + else (vb.toInt & 0x3) + (s.toInt & 0x1) - 3 + } >>> 31) + s + exp -= expShift } - ) { - val s4 = s << 2 - val uin = (vbls - s4).toInt - dv = (~ { - if ((((s4 + vbrd).toInt + 4) ^ uin) < 0) uin - else (vb.toInt & 0x3) + (s.toInt & 0x1) - 3 - } >>> 31) + s - exp -= expShift } + val len = digitCount(dv) + exp += len - 1 + if (exp < -3 || exp >= 7) { + val dotOff = s.length + 1 + s.append(dv) + var i = s.length - 1 + while (i > dotOff && s.charAt(i) == '0') i -= 1 + s.setLength(i + 1) + s.insert(dotOff, '.').append('E').append(exp) + } else if (exp < 0) { + s.append('0').append('.') + while ({ + exp += 1 + exp != 0 + }) s.append('0') + s.append(dv) + var i = s.length - 1 + while (s.charAt(i) == '0') i -= 1 + s.setLength(i + 1) + s + } else if (exp + 1 < len) { + val dotOff = s.length + exp + 1 + s.append(dv) + var i = s.length - 1 + while (s.charAt(i) == '0') i -= 1 + s.setLength(i + 1) + s.insert(dotOff, '.') + } else s.append(dv).append('.').append('0') } - val len = digitCount(dv) - exp += len - 1 - if (exp < -3 || exp >= 7) { - val dotOff = s.length + 1 - s.append(dv) - var i = s.length - 1 - while (i > dotOff && s.charAt(i) == '0') i -= 1 - s.setLength(i + 1) - s.insert(dotOff, '.').append('E').append(exp) - } else if (exp < 0) { - s.append('0').append('.') - while ({ - exp += 1 - exp != 0 - }) s.append('0') - s.append(dv) - var i = s.length - 1 - while (s.charAt(i) == '0') i -= 1 - s.setLength(i + 1) - s - } else if (exp + 1 < len) { - val dotOff = s.length + exp + 1 - s.append(dv) - var i = s.length - 1 - while (s.charAt(i) == '0') i -= 1 - s.setLength(i + 1) - s.insert(dotOff, '.') - } else s.append(dv).append('.').append('0') - } - }.toString + }.toString } def toString(x: Float): String = { @@ -197,90 +193,91 @@ object SafeNumbers { if (x != x) """"NaN"""" else if (bits < 0) """"-Infinity"""" else """"Infinity"""" - } else { - val s = new java.lang.StringBuilder(16) - if (bits < 0) s.append('-') - if (x == 0.0f) s.append('0').append('.').append('0') - else { - var e = ieeeExponent - 150 - var m = ieeeMantissa | 0x800000 - var dv, exp = 0 - if (e == 0) dv = m - else if (e >= -23 && e < 0 && m << e == 0) dv = m >> -e + } else + { + val s = new java.lang.StringBuilder(16) + if (bits < 0) s.append('-') + if (x == 0.0f) s.append('0').append('.').append('0') else { - var expShift, expCorr = 0 - var cblShift = 2 - if (ieeeExponent == 0) { - e = -149 - m = ieeeMantissa - if (ieeeMantissa < 8) { - m *= 10 - expShift = 1 + var e = ieeeExponent - 150 + var m = ieeeMantissa | 0x800000 + var dv, exp = 0 + if (e == 0) dv = m + else if (e >= -23 && e < 0 && m << e == 0) dv = m >> -e + else { + var expShift, expCorr = 0 + var cblShift = 2 + if (ieeeExponent == 0) { + e = -149 + m = ieeeMantissa + if (ieeeMantissa < 8) { + m *= 10 + expShift = 1 + } + } else if (ieeeMantissa == 0 && ieeeExponent > 1) { + expCorr = 131007 + cblShift = 1 } - } else if (ieeeMantissa == 0 && ieeeExponent > 1) { - expCorr = 131007 - cblShift = 1 - } - exp = e * 315653 - expCorr >> 20 - val g1 = gs(exp + 324 << 1) + 1 - val h = (-exp * 108853 >> 15) + e + 1 - val cb = m << 2 - val outm1 = (m & 0x1) - 1 - val vb = rop(g1, cb << h) - val vbls = rop(g1, cb - cblShift << h) + outm1 - val vbrd = outm1 - rop(g1, cb + 2 << h) - val s = vb >> 2 - if ( - s < 100 || { - dv = (s * 3435973837L >>> 35).toInt // divide a positive int by 10 - val sp40 = dv * 40 - val upin = vbls - sp40 - ((sp40 + vbrd + 40) ^ upin) >= 0 || { - dv += ~upin >>> 31 - exp += 1 - false + exp = e * 315653 - expCorr >> 20 + val g1 = gs(exp + 324 << 1) + 1 + val h = (-exp * 108853 >> 15) + e + 1 + val cb = m << 2 + val outm1 = (m & 0x1) - 1 + val vb = rop(g1, cb << h) + val vbls = rop(g1, cb - cblShift << h) + outm1 + val vbrd = outm1 - rop(g1, cb + 2 << h) + val s = vb >> 2 + if ( + s < 100 || { + dv = (s * 3435973837L >>> 35).toInt // divide a positive int by 10 + val sp40 = dv * 40 + val upin = vbls - sp40 + ((sp40 + vbrd + 40) ^ upin) >= 0 || { + dv += ~upin >>> 31 + exp += 1 + false + } } + ) { + val s4 = s << 2 + val uin = vbls - s4 + dv = (~ { + if (((s4 + vbrd + 4) ^ uin) < 0) uin + else (vb & 0x3) + (s & 0x1) - 3 + } >>> 31) + s + exp -= expShift } - ) { - val s4 = s << 2 - val uin = vbls - s4 - dv = (~ { - if (((s4 + vbrd + 4) ^ uin) < 0) uin - else (vb & 0x3) + (s & 0x1) - 3 - } >>> 31) + s - exp -= expShift } + val len = digitCount(dv.toLong) + exp += len - 1 + if (exp < -3 || exp >= 7) { + val dotOff = s.length + 1 + s.append(dv) + var i = s.length - 1 + while (i > dotOff && s.charAt(i) == '0') i -= 1 + s.setLength(i + 1) + s.insert(dotOff, '.').append('E').append(exp) + } else if (exp < 0) { + s.append('0').append('.') + while ({ + exp += 1 + exp != 0 + }) s.append('0') + s.append(dv) + var i = s.length - 1 + while (s.charAt(i) == '0') i -= 1 + s.setLength(i + 1) + s + } else if (exp + 1 < len) { + val dotOff = s.length + exp + 1 + s.append(dv) + var i = s.length - 1 + while (s.charAt(i) == '0') i -= 1 + s.setLength(i + 1) + s.insert(dotOff, '.') + } else s.append(dv).append('.').append('0') } - val len = digitCount(dv.toLong) - exp += len - 1 - if (exp < -3 || exp >= 7) { - val dotOff = s.length + 1 - s.append(dv) - var i = s.length - 1 - while (i > dotOff && s.charAt(i) == '0') i -= 1 - s.setLength(i + 1) - s.insert(dotOff, '.').append('E').append(exp) - } else if (exp < 0) { - s.append('0').append('.') - while ({ - exp += 1 - exp != 0 - }) s.append('0') - s.append(dv) - var i = s.length - 1 - while (s.charAt(i) == '0') i -= 1 - s.setLength(i + 1) - s - } else if (exp + 1 < len) { - val dotOff = s.length + exp + 1 - s.append(dv) - var i = s.length - 1 - while (s.charAt(i) == '0') i -= 1 - s.setLength(i + 1) - s.insert(dotOff, '.') - } else s.append(dv).append('.').append('0') - } - }.toString + }.toString } private[this] def rop(g1: Long, g0: Long, cp: Long): Long = { diff --git a/zio-json/shared/src/main/scala/zio/json/internal/readers.scala b/zio-json/shared/src/main/scala/zio/json/internal/readers.scala index 6d81eb43a..95a715b8f 100644 --- a/zio-json/shared/src/main/scala/zio/json/internal/readers.scala +++ b/zio-json/shared/src/main/scala/zio/json/internal/readers.scala @@ -75,9 +75,8 @@ private[zio] final class RewindTwice /** * A Reader that can retract and replay the last char that it read. * - * This is essential when parsing contents that do not have a terminator - * character, e.g. numbers, whilst preserving the non-significant character for - * further processing. + * This is essential when parsing contents that do not have a terminator character, e.g. numbers, whilst preserving the + * non-significant character for further processing. */ sealed trait RetractReader extends OneCharReader { @@ -150,13 +149,11 @@ final class WithRetractReader(in: java.io.Reader) extends RetractReader with Aut } /** - * Records the contents of an underlying Reader and allows rewinding back to - * the beginning once. If rewound and reading continues past the - * recording, the recording no longer continues. + * Records the contents of an underlying Reader and allows rewinding back to the beginning once. If rewound and reading + * continues past the recording, the recording no longer continues. * - * To avoid feature interaction edge cases, `retract` is not allowed as the - * first action nor is `retract` allowed to happen immediately before or after - * a `rewind`. + * To avoid feature interaction edge cases, `retract` is not allowed as the first action nor is `retract` allowed to + * happen immediately before or after a `rewind`. */ private[zio] sealed trait RecordingReader extends RetractReader { def rewind(): Unit diff --git a/zio-json/shared/src/main/scala/zio/json/javatime/serializers.scala b/zio-json/shared/src/main/scala/zio/json/javatime/serializers.scala index 9fa4365b7..4f58364c6 100644 --- a/zio-json/shared/src/main/scala/zio/json/javatime/serializers.scala +++ b/zio-json/shared/src/main/scala/zio/json/javatime/serializers.scala @@ -57,9 +57,9 @@ private[json] object serializers { val epochDay = (if (epochSecond >= 0) epochSecond else epochSecond - 86399) / 86400 // 86400 == seconds per day - val secsOfDay = (epochSecond - epochDay * 86400).toInt - var marchZeroDay = epochDay + 719468 // 719468 == 719528 - 60 == days 0000 to 1970 - days 1st Jan to 1st Mar - var adjustYear = 0 + val secsOfDay = (epochSecond - epochDay * 86400).toInt + var marchZeroDay = epochDay + 719468 // 719468 == 719528 - 60 == days 0000 to 1970 - days 1st Jan to 1st Mar + var adjustYear = 0 if (marchZeroDay < 0) { // adjust negative years to positive for calculation val adjust400YearCycles = to400YearCycle(marchZeroDay + 1) - 1 adjustYear = adjust400YearCycles * 400 diff --git a/zio-json/shared/src/main/scala/zio/json/package.scala b/zio-json/shared/src/main/scala/zio/json/package.scala index b9747b164..7ec99fd4a 100644 --- a/zio-json/shared/src/main/scala/zio/json/package.scala +++ b/zio-json/shared/src/main/scala/zio/json/package.scala @@ -32,11 +32,9 @@ package object json extends JsonPackagePlatformSpecific { /** * Attempts to decode the raw JSON string as an `A`. * - * On failure a human readable message is returned using a jq friendly - * format. For example the error - * `.rows[0].elements[0].distance.value(missing)"` tells us the location of a - * missing field named "value". We can use part of the error message in the - * `jq` command line tool for further inspection, e.g. + * On failure a human readable message is returned using a jq friendly format. For example the error + * `.rows[0].elements[0].distance.value(missing)"` tells us the location of a missing field named "value". We can + * use part of the error message in the `jq` command line tool for further inspection, e.g. * * {{{jq '.rows[0].elements[0].distance' input.json}}} */ diff --git a/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala b/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala index 2cafc819f..bb1f8b695 100644 --- a/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala +++ b/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala @@ -1,5 +1,6 @@ package zio.json +import zio.Scope import zio.json.JsonCodecConfiguration.SumTypeHandling.DiscriminatorField import zio.json.ast.Json import zio.test._ @@ -16,7 +17,7 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { case class OptionalField(a: Option[Int]) - def spec = suite("ConfigurableDeriveCodecSpec")( + def spec: Spec[Environment with TestEnvironment with Scope, Any] = suite("ConfigurableDeriveCodecSpec")( suite("defaults")( suite("string")( test("should not map field names by default") { diff --git a/zio-json/shared/src/test/scala-3/zio/json/DerivedCodecSpec.scala b/zio-json/shared/src/test/scala-3/zio/json/DerivedCodecSpec.scala index 84d1f2313..433042bd5 100644 --- a/zio-json/shared/src/test/scala-3/zio/json/DerivedCodecSpec.scala +++ b/zio-json/shared/src/test/scala-3/zio/json/DerivedCodecSpec.scala @@ -32,6 +32,6 @@ object DerivedCodecSpec extends ZIOSpecDefault { case class Foo(aOrB: "A" | "B", optA: Option["A"]) derives JsonCodec assertTrue(Foo("A", Some("A")).toJson.fromJson[Foo] == Right(Foo("A", Some("A")))) - }, + } ) } diff --git a/zio-json/shared/src/test/scala-3/zio/json/DerivedDecoderSpec.scala b/zio-json/shared/src/test/scala-3/zio/json/DerivedDecoderSpec.scala index b99dd7cd2..d599c5b72 100644 --- a/zio-json/shared/src/test/scala-3/zio/json/DerivedDecoderSpec.scala +++ b/zio-json/shared/src/test/scala-3/zio/json/DerivedDecoderSpec.scala @@ -22,7 +22,7 @@ object DerivedDecoderSpec extends ZIOSpecDefault { case Qux val result = "\"Qux\"".fromJson[Foo] - + assertTrue(result == Right(Foo.Qux)) }, test("Derives for a sum sealed trait Enumeration type") { @@ -33,7 +33,7 @@ object DerivedDecoderSpec extends ZIOSpecDefault { case object Qux extends Foo val result = "\"Qux\"".fromJson[Foo] - + assertTrue(result == Right(Foo.Qux)) }, test("Derives for a sum sealed trait Enumeration type with discriminator") { @@ -45,7 +45,7 @@ object DerivedDecoderSpec extends ZIOSpecDefault { case object Qux extends Foo val result = """{"$type":"Qux"}""".fromJson[Foo] - + assertTrue(result == Right(Foo.Qux)) }, test("Derives for a sum ADT type") { diff --git a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala index 468017ecc..39057f569 100644 --- a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala @@ -7,7 +7,6 @@ import zio.test.Assertion._ import zio.test.TestAspect.jvmOnly import zio.test._ -import java.math.BigInteger import scala.collection.immutable object CodecSpec extends ZIOSpecDefault { @@ -37,7 +36,7 @@ object CodecSpec extends ZIOSpecDefault { ) }, test("primitives") { - val exampleBDString = "234234.234" + // this big integer consumes more than 256 bits assert( "170141183460469231731687303715884105728489465165484668486513574864654818964653168465316546851" diff --git a/zio-json/shared/src/test/scala/zio/json/Gens.scala b/zio-json/shared/src/test/scala/zio/json/Gens.scala index 354140c64..dd605640a 100644 --- a/zio-json/shared/src/test/scala/zio/json/Gens.scala +++ b/zio-json/shared/src/test/scala/zio/json/Gens.scala @@ -2,58 +2,59 @@ package testzio.json import zio.test.Gen -import java.time._ +import java.math.BigInteger +import java.time.{ ZoneId, _ } import scala.jdk.CollectionConverters._ import scala.util.Try object Gens { - val genBigInteger = + val genBigInteger: Gen[Any, BigInteger] = Gen .bigInt((BigInt(2).pow(128) - 1) * -1, BigInt(2).pow(128) - 1) .map(_.bigInteger) .filter(_.bitLength < 128) - val genBigDecimal = + val genBigDecimal: Gen[Any, java.math.BigDecimal] = Gen .bigDecimal((BigDecimal(2).pow(128) - 1) * -1, BigDecimal(2).pow(128) - 1) .map(_.bigDecimal) .filter(_.toBigInteger.bitLength < 128) - val genUsAsciiString = + val genUsAsciiString: Gen[Any, String] = Gen.string(Gen.oneOf(Gen.char('!', '~'))) - val genAlphaLowerString = + val genAlphaLowerString: Gen[Any, String] = Gen.string(Gen.oneOf(Gen.char('a', 'z'))) - val genYear = + val genYear: Gen[Any, Year] = Gen.oneOf(Gen.int(-9999, 9999), Gen.int(-999999999, 999999999)).map(Year.of) - val genLocalDate = for { + val genLocalDate: Gen[Any, LocalDate] = for { year <- genYear month <- Gen.int(1, 12) day <- Gen.int(1, Month.of(month).length(year.isLeap)) } yield LocalDate.of(year.getValue, month, day) - val genLocalTime = for { + val genLocalTime: Gen[Any, LocalTime] = for { hour <- Gen.int(0, 23) minute <- Gen.int(0, 59) second <- Gen.int(0, 59) nano <- Gen.int(0, 999999999) } yield LocalTime.of(hour, minute, second, nano) - val genInstant = for { + val genInstant: Gen[Any, Instant] = for { epochSecond <- Gen.long(Instant.MIN.getEpochSecond, Instant.MAX.getEpochSecond) nanoAdjustment <- Gen.long(Long.MinValue, Long.MaxValue) fallbackInstant <- Gen.elements(Instant.MIN, Instant.EPOCH, Instant.MAX) } yield Try(Instant.ofEpochSecond(epochSecond, nanoAdjustment)).getOrElse(fallbackInstant) - val genZoneOffset = Gen.oneOf( + val genZoneOffset: Gen[Any, ZoneOffset] = Gen.oneOf( Gen.int(-18, 18).map(ZoneOffset.ofHours), Gen.int(-18 * 60, 18 * 60).map(x => ZoneOffset.ofHoursMinutes(x / 60, x % 60)), Gen.int(-18 * 60 * 60, 18 * 60 * 60).map(ZoneOffset.ofTotalSeconds) ) - val genZoneId = Gen.oneOf( + val genZoneId: Gen[Any, ZoneId] = Gen.oneOf( genZoneOffset, genZoneOffset.map(zo => ZoneId.ofOffset("UT", zo)), genZoneOffset.map(zo => ZoneId.ofOffset("UTC", zo)), @@ -62,17 +63,17 @@ object Gens { Gen.elements(ZoneId.SHORT_IDS.values().asScala.toSeq: _*).map(ZoneId.of) ) - val genLocalDateTime = for { + val genLocalDateTime: Gen[Any, LocalDateTime] = for { localDate <- genLocalDate localTime <- genLocalTime } yield LocalDateTime.of(localDate, localTime) - val genZonedDateTime = for { + val genZonedDateTime: Gen[Any, ZonedDateTime] = for { localDateTime <- genLocalDateTime zoneId <- genZoneId } yield ZonedDateTime.of(localDateTime, zoneId) - val genDuration = Gen.oneOf( + val genDuration: Gen[Any, Duration] = Gen.oneOf( Gen.long(Long.MinValue / 86400, Long.MaxValue / 86400).map(Duration.ofDays), Gen.long(Long.MinValue / 3600, Long.MaxValue / 3600).map(Duration.ofHours), Gen.long(Long.MinValue / 60, Long.MaxValue / 60).map(Duration.ofMinutes), @@ -81,33 +82,33 @@ object Gens { Gen.long(Int.MinValue, Int.MaxValue.toLong).map(Duration.ofNanos) ) - val genMonthDay = for { + val genMonthDay: Gen[Any, MonthDay] = for { month <- Gen.int(1, 12) day <- Gen.int(1, 29) } yield MonthDay.of(month, day) - val genOffsetDateTime = for { + val genOffsetDateTime: Gen[Any, OffsetDateTime] = for { localDateTime <- genLocalDateTime zoneOffset <- genZoneOffset } yield OffsetDateTime.of(localDateTime, zoneOffset) - val genOffsetTime = for { + val genOffsetTime: Gen[Any, OffsetTime] = for { localTime <- genLocalTime zoneOffset <- genZoneOffset } yield OffsetTime.of(localTime, zoneOffset) - val genPeriod = for { + val genPeriod: Gen[Any, Period] = for { year <- Gen.int month <- Gen.int day <- Gen.int } yield Period.of(year, month, day) - val genYearMonth = for { + val genYearMonth: Gen[Any, YearMonth] = for { year <- genYear month <- Gen.int(1, 12) } yield YearMonth.of(year.getValue, month) - val genDayOfWeek = Gen.int(1, 7).map(DayOfWeek.of) + val genDayOfWeek: Gen[Any, DayOfWeek] = Gen.int(1, 7).map(DayOfWeek.of) - val genMonth = Gen.int(1, 12).map(Month.of) + val genMonth: Gen[Any, Month] = Gen.int(1, 12).map(Month.of) } diff --git a/zio-json/shared/src/test/scala/zio/json/codegen/GeneratorSpec.scala b/zio-json/shared/src/test/scala/zio/json/codegen/GeneratorSpec.scala index aff2e766e..fdc6b7b42 100644 --- a/zio-json/shared/src/test/scala/zio/json/codegen/GeneratorSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/codegen/GeneratorSpec.scala @@ -1,11 +1,12 @@ package zio.json.codegen +import zio.Scope import zio.json._ import zio.json.ast.Json import zio.test._ object GeneratorSpec extends ZIOSpecDefault { - def spec = suite("GeneratorSpec")( + def spec: Spec[Environment with TestEnvironment with Scope, Any] = suite("GeneratorSpec")( suite("generates case classes from JSON strings")( test("simple object") { val json =