Skip to content

Commit

Permalink
Merge pull request #181 from vpavkin/scala3
Browse files Browse the repository at this point in the history
Scala 3 support
  • Loading branch information
FunFunFine authored Aug 6, 2021
2 parents bfcd3b3 + 4bd3927 commit b9a4bd6
Show file tree
Hide file tree
Showing 39 changed files with 1,184 additions and 1,577 deletions.
Empty file added .scalafix.conf
Empty file.
5 changes: 5 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version = 3.0.0-RC6
runner.dialect = scala3
rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = oldSyntaxToo
rewrite.rules = [AvoidInfix, RedundantBraces, RedundantParens, SortModifiers, ]
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ sudo: true
language: scala

scala:
- 2.12.12
- 2.13.4
- 3.0.1

jdk:
- openjdk8
Expand Down
78 changes: 39 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
# circe-magnolia
### Codec derivation for [Circe](circe.io) using [Magnolia](http://magnolia.work/).
# circe-magnolia — **Scala 3 version**
This is the only configurable derivation for circe for now.

## Codec derivation for [Circe](circe.io) using [softwaremill.Magnolia](https://github.com/softwaremill/magnolia).

[![Build Status](https://img.shields.io/travis/circe/circe-magnolia/master.svg)](https://travis-ci.org/circe/circe-magnolia)
[![Coverage status](https://img.shields.io/codecov/c/github/circe/circe-magnolia/master.svg)](https://codecov.io/github/circe/circe-magnolia?branch=master)
[![Maven Central](https://img.shields.io/maven-central/v/io.circe/circe-magnolia-derivation_2.12.svg)](https://search.maven.org/artifact/io.circe/circe-magnolia-derivation_2.12)

This library provides facilities to derive JSON codec instances for Circe using Magnolia macros.

### ⚠️ Early development status warning
## Scala 3 limitations
Currently, non-Mirror-able types are not supported. Full list of unsupported stuff can be found [here](https://dotty.epfl.ch/docs/reference/contextual/derivation.html#types-supporting-derives-clauses).

Also, recursive types may not fully work either along with default values.

Overriding codecs for subtypes of ADTs is not supported yet.

## Scala 2
See the Scala 2 support [here at master branch](https://github.com/vpavkin/circe-magnolia).



## ⚠️ Early development status warning
Although this project is extensively tested and seems to work fine, it's still at early development stages.
It's not advised to use this in production without proper test coverage of related code.

There are still some things, that are different from circe-generic, including a critical issue with auto derivation.

See [Testing](#testing) and [Status](#status) for more details.

### Getting started
## Getting started

To play around with circe-magnolia, add it to your build:

Expand All @@ -27,7 +41,7 @@ After that, as in `circe-generic`, you can use one of two derivation modes.

Note, that at the moment for both auto and semiauto modes you have to import encoder and decoder machinery separately (see examples below).

#### Auto
### Auto

Works in the same way as `io.circe.generic.auto._` from `circe-generic`.

Expand All @@ -42,49 +56,49 @@ implicitly[Decoder[Foo]]
```

#### Semiauto
### Semiauto

Works in the same way as `io.circe.generic.semiauto._` from `circe-generic`, but the method names differ so that you can theoretically use both in the same scope:
Works in the same way as `io.circe.generic.semiauto.*` from `circe-generic`, but the method names differ so that you can theoretically use both in the same scope:

```
import io.circe.magnolia.derivation.decoder.semiauto._
import io.circe.magnolia.derivation.encoder.semiauto._
import io.circe.magnolia.derivation.decoder.semiauto.*
import io.circe.magnolia.derivation.encoder.semiauto.*
case class Foo(i: Int, s: String)
val encoder = deriveMagnoliaEncoder[Foo]
val decoder = deriveMagnoliaDecoder[Foo]
given Encoder[Foo] = deriveMagnoliaEncoder[Foo]
given Decoder[Foo] = deriveMagnoliaDecoder[Foo]
```

#### Configurable Semiauto and Auto derivations

Configuration is possible if you want to configure your Encoder/Decoder's output. This project provides the same configuration as `circe-generic-extras`.
Configuration is possible if you want to configure your Encoder/Decoder's output.

For example, to generate Encoder/Decoder where the JSON keys are snake-cased:
```
import io.circe.magnolia.configured.decoder.semiauto._
import io.circe.magnolia.configured.encoder.semiauto._
import io.circe.magnolia.configured.decoder.semiauto.*
import io.circe.magnolia.configured.encoder.semiauto.*
import io.circe.magnolia.configured.Configuration
case class User(firstName: Int, lastName: String)
implicit val configuration: Configuration = Configuration.default.withSnakeCaseMemberNames
given Configuration = Configuration.default.withSnakeCaseMemberNames
val encoder = deriveConfiguredMagnoliaEncoder[User]
val decoder = deriveConfiguredMagnoliaDecoder[User]
given Encoder[User] = deriveConfiguredMagnoliaEncoder[User]
given Decoder[User] = deriveConfiguredMagnoliaDecoder[User]
```

To avoid constantly needing to provide/import an implicit `Configuration` to derive Encoder/Decoders, you can hardcode the configuration by defining your own version of deriver.
See [HardcodedDerivationSpec](https://github.com/circe/circe-magnolia/blob/master/tests/src/test/scala/io/circe/magnolia/configured/HardcodedDerivationSpec.scala) for an example of how to do this. (In fact, the default auto and semiauto derivers are hardcoded to use the default Configuration)
To avoid constantly needing to provide/import an implicit `Configuration` to derive Encoder/Decoders, you can hard-code the configuration by defining your own version of derivation.
See [HardcodedDerivationSpec](https://github.com/circe/circe-magnolia/blob/master/tests/src/test/scala/io/circe/magnolia/configured/HardcodedDerivationSpec.scala) for an example of how to do this. (In fact, the default auto and semiauto derivations are hardcoded to use the default Configuration)

### Testing

To ensure `circe-magnolia` derivation and codecs work in the same way as in `circe-generic`, several test suites from original circe repository were adapted and added to this project. These tests validate the derivation semantics and also the lawfulness of derived codecs ([example](https://github.com/circe/circe-magnolia/blob/master/tests/src/test/scala/io/circe/magnolia/AutoDerivedSuite.scala)).

There's another set of tests, that validate the equivalence of JSON and decoding logic, produced by `circe-magnolia` and `circe-generic` ([example](https://github.com/circe/circe-magnolia/blob/master/tests/src/test/scala/io/circe/magnolia/AutoDerivedEquivalenceSuite.scala)).

Test suite is currently green, but couple of cases are worked around or ignored, and waiting to be fixed. See the issue tracker for outstanding issues.
Test suite is currently green, but a couple of cases are worked around or ignored, and waiting to be fixed. See the issue tracker for outstanding issues.

### Status

Expand All @@ -94,20 +108,7 @@ There is a subtle difference from circe-generic semiauto in what can and what ca

In essense: current version of `circe-magnolia` semiauto is as powerful as `circe-generic`'s auto under the hood. It just doesn't provide any top-level implicits out of the box - you have to call `deriveMagnolia[Encoder|Decoder]` to start derivation process. See more [here](https://github.com/propensive/magnolia/issues/105)

**Auto** derivation also works, but there's a twist: at the moment, for default codecs (for example, `Encoder[List]`) to be picked up, they have to be imported explicitly.
Otherwise, deriver uses Magnolia to derive codecs for any types which are either case classes or sealed traits. These derived codecs then override the default ones.

This is definitely going to be fixed in future, but if you want to switch from circe-generic's auto to circe-magnolia's auto today, you would have to add additional imports to every place where derivation takes place:

```scala
// import all default codecs, defined in circe
import io.circe.Encoder._
import io.circe.Decoder._
// also import all codecs, defined in the companion objects of your own data definitions
import Foo._, Bar._
```

Another outstanding issue with auto is that it doesn't pick up default instances for tagged types.
Another outstanding issue with auto is that it doesn't pick up default arguments for case classes.

### FAQ

Expand All @@ -119,12 +120,12 @@ This is probably due to trying to derive for indirect recursive type like this:
case class RecursiveWithOptionExample(o: Option[RecursiveWithOptionExample])
object RecursiveWithOptionExample {
implicit val decoder: Decoder[RecursiveWithOptionExample] = deriveMagnoliaDecoder[RecursiveWithOptionExample]
given decoder: Decoder[RecursiveWithOptionExample] = deriveMagnoliaDecoder[RecursiveWithOptionExample]
}
```

The cause is that when the Scala compiler tries to resolve `Decoder[Option[RecursiveWithOptionExample]]`, it finds
and use the same decoder instance we're trying to define! (if you use `implicit def` instead, you'll see StackOverflowException for the same reason)
and use the same decoder instance we're trying to define! (if you use `given` instead, you'll see StackOverflowException for the same reason)

The fix is to make your definition `lazy`.

Expand All @@ -134,7 +135,7 @@ implicit lazy val decoder: Decoder[RecursiveWithOptionExample] = deriveMagnoliaD

### Further work

1) Facilitate [magnolia development](https://github.com/propensive/magnolia/issues/107) to make auto derivation work the same way as in `circe-generic`.
1) Facilitate [magnolia development](https://github.com/softwaremill/magnolia) to make auto derivation work the same way as in `circe-generic`.
2) Add derivation of partial/patch codecs.

### Contributors
Expand All @@ -144,10 +145,9 @@ Circe-magnolia is currently developed and maintained by [Vladimir Pavkin](https:
I really welcome any kind of contributions, including test/bug reports and benchmarks.

I really appreciate all the people who contributed to the project:
* [Jon Pretty](https://github.com/propensive)
* [Artsiom Miklushou](https://github.com/mikla)

I also want to say "Thank you!" to

* [Jon Pretty](https://github.com/propensive) for active collaboration and improvements in Magnolia, that make this project progress.
* [Jon Pretty](https://github.com/propensive) for active collaboration and improvements in Magnolia, that make this project started.
* [Travis Brown](https://github.com/travisbrown) for his amazing Circe project.
118 changes: 28 additions & 90 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,102 +3,40 @@ import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}

lazy val buildSettings = Seq(
organization := "io.circe",
scalaVersion := "2.12.12",
crossScalaVersions := List("2.12.12", "2.13.4")
scalaVersion := "3.0.0"
)

lazy val magnoliaVersion = "0.17.0"
lazy val circeVersion = "0.13.0"
lazy val circeGenericExtrasVersion = "0.13.0"
lazy val shapelessVersion = "2.3.3"
lazy val scalatestVersion = "3.2.2"
lazy val scalacheckVersion = "1.14.3"
lazy val magnoliaVersion = "0.0.1-M4"
lazy val circeVersion = "0.15.0-M1"
lazy val shapelessVersion = "3.0.2"
lazy val scalatestVersion = "3.2.9"
lazy val scalacheckVersion = "1.15.4"

lazy val compilerSettings = Seq(
scalacOptions ++= Seq(
"-deprecation",
"-encoding", "utf-8",
"-explaintypes",
"-feature",
"-language:existentials",
"-language:experimental.macros",
"-language:higherKinds",
"-language:implicitConversions",
"-unchecked",
"-Xcheckinit",
"-Xfatal-warnings",
"-Xlint:adapted-args",
"-Xlint:delayedinit-select",
"-Xlint:doc-detached",
"-Xlint:inaccessible",
"-Xlint:infer-any",
"-Xlint:missing-interpolator",
"-Xlint:nullary-override",
"-Xlint:nullary-unit",
"-Xlint:option-implicit",
"-Xlint:package-object-classes",
"-Xlint:poly-implicit-overload",
"-Xlint:private-shadow",
"-Xlint:stars-align",
"-Xlint:type-parameter-shadow",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Xlint:constant",
"-Ywarn-macros:after",
"-Ywarn-extra-implicit",
"-Ywarn-unused:implicits",
"-Ywarn-unused:imports",
"-Ywarn-unused:locals",
"-Ywarn-unused:params",
"-Ywarn-unused:patvars",
"-Ywarn-unused:privates"),
scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, y)) if y == 12 => Seq(
"-Xfuture",
"-Xlint:by-name-right-associative",
"-Xlint:unsound-match",
"-Yno-adapted-args",
"-Ypartial-unification",
"-Ywarn-inaccessible",
"-Ywarn-infer-any",
"-Ywarn-nullary-override",
"-Ywarn-nullary-unit",
)
case Some((2, y)) if y == 13 => Seq("-Ymacro-annotations")
case _ => Seq.empty[String]
}
},
scalacOptions in(Compile, console) --= Seq("-Ywarn-unused:imports", "-Xfatal-warnings")
)

lazy val allSettings = buildSettings ++ compilerSettings ++ publishSettings
lazy val allSettings = buildSettings ++ publishSettings

lazy val coreDependencies = libraryDependencies ++= Seq(
"io.circe" %%% "circe-core" % circeVersion,
"com.propensive" %%% "magnolia" % magnoliaVersion,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"com.softwaremill.magnolia1_3" %% "magnolia" % "1.0.0-M4"
)

lazy val testDependencies = libraryDependencies ++= Seq(
"com.chuusai" %% "shapeless" % shapelessVersion,
"io.circe" %%% "circe-parser" % circeVersion,
"io.circe" %%% "circe-generic" % circeVersion,
"io.circe" %%% "circe-generic-extras" % circeGenericExtrasVersion,
"io.circe" %%% "circe-testing" % circeVersion,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion,
"org.scalatest" %%% "scalatest" % scalatestVersion
"org.scalatest" %%% "scalatest" % scalatestVersion,
"org.scalatestplus" %% "scalacheck-1-15" % "3.2.9.0"
)

lazy val circeMagnolia = project.in(file("."))
lazy val circeMagnolia = project
.in(file("."))
.settings(name := "circe-magnolia")
.settings(allSettings: _*)
.settings(noPublishSettings: _*)
.aggregate(derivationJVM, derivationJS, testsJS, testsJVM)
.dependsOn(derivationJVM, derivationJS, testsJS, testsJVM)
.aggregate(derivation)
.dependsOn(derivation)

lazy val derivation = crossProject(JSPlatform, JVMPlatform).crossType(CrossType.Pure)
lazy val derivation = project
.in(file("derivation"))
.settings(
description := "Magnolia-based derivation for Circe codecs",
Expand All @@ -108,10 +46,7 @@ lazy val derivation = crossProject(JSPlatform, JVMPlatform).crossType(CrossType.
.settings(allSettings: _*)
.settings(coreDependencies)

lazy val derivationJVM = derivation.jvm
lazy val derivationJS = derivation.js

lazy val tests = crossProject(JSPlatform, JVMPlatform).crossType(CrossType.Pure)
lazy val tests = project
.in(file("tests"))
.settings(
description := "Circe-magnolia tests",
Expand All @@ -126,12 +61,8 @@ lazy val tests = crossProject(JSPlatform, JVMPlatform).crossType(CrossType.Pure)
// Coverage disabled due to https://github.com/scoverage/scalac-scoverage-plugin/issues/269
// coverageExcludedPackages :=".*"
)
.jsConfigure(_.enablePlugins(ScalaJSBundlerPlugin))
.dependsOn(derivation)

lazy val testsJVM = tests.jvm
lazy val testsJS = tests.js

lazy val noPublishSettings = Seq(
publish := {},
publishLocal := {},
Expand All @@ -143,16 +74,18 @@ lazy val publishSettings = Seq(
releaseCrossBuild := true,
releasePublishArtifactsAction := PgpKeys.publishSigned.value,
homepage := Some(url("https://github.com/circe/circe-magnolia")),
licenses := Seq("Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
licenses := Seq(
"Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")
),
publishMavenStyle := true,
publishArtifact in Test := false,
Test / publishArtifact := false,
pomIncludeRepository := { _ => false },
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
Some("snapshots".at(nexus + "content/repositories/snapshots"))
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
Some("releases".at(nexus + "service/local/staging/deploy/maven2"))
},
autoAPIMappings := true,
apiURL := Some(url("https://vpavkin.github.io/circe-magnolia/api/")),
Expand All @@ -169,6 +102,11 @@ lazy val publishSettings = Seq(
<name>Vladimir Pavkin</name>
<url>http://pavkin.ru</url>
</developer>
<developer>
<id>funfunfine</id>
<name>Anton Voytsishevskiy</name>
<url>https://t.me/funfunfine</url>
</developer>
</developers>
)

Expand All @@ -189,4 +127,4 @@ lazy val sharedReleaseProcess = Seq(
)
)

addCommandAlias("validate", ";compile;testsJVM/test;testsJS/test")
addCommandAlias("validate", ";compile;tests/test")
1 change: 0 additions & 1 deletion derivation/src/main/scala/io/circe/magnolia/JsonKey.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ package io.circe.magnolia
import scala.annotation.StaticAnnotation

final case class JsonKey(value: String) extends StaticAnnotation

Loading

0 comments on commit b9a4bd6

Please sign in to comment.