From 2ce7a3f35331cdbce07516ccf1e3c74998f7040d Mon Sep 17 00:00:00 2001 From: Raphael Parree Date: Wed, 1 Nov 2023 13:18:12 +0100 Subject: [PATCH 1/2] feat: added support for upickle --- build.sc | 22 ++++++++- .../src/io/github/iltotore/iron/upickle.scala | 49 +++++++++++++++++++ .../github/iltotore/iron/uPickleSuite.scala | 36 ++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 upickle/src/io/github/iltotore/iron/upickle.scala create mode 100644 upickle/test/src/io/github/iltotore/iron/uPickleSuite.scala diff --git a/build.sc b/build.sc index 423fcb7a..b3776bd7 100644 --- a/build.sc +++ b/build.sc @@ -77,7 +77,7 @@ object docs extends BaseModule { def artifactName = "iron-docs" - val modules: Seq[ScalaModule] = Seq(main, cats, circe, ciris, jsoniter, scalacheck, zio, zioJson) + val modules: Seq[ScalaModule] = Seq(main, cats, circe, upickle, ciris, jsoniter, scalacheck, zio, zioJson) def docSources = T.sources { T.traverse(modules)(_.docSources)().flatten @@ -282,6 +282,26 @@ object cats extends SubModule { object native extends NativeCrossModule } +object upickle extends SubModule { + + def artifactName = "iron-upickle" + + def ivyDeps = Agg( + ivy"com.lihaoyi::upickle:3.1.3" + ) + + object test extends Tests { + def ivyDeps = Agg( + ivy"com.lihaoyi::utest:0.8.1", + ivy"com.lihaoyi::upickle:3.1.3" + ) + } + + object js extends JSCrossModule + + object native extends NativeCrossModule +} + object circe extends SubModule { def artifactName = "iron-circe" diff --git a/upickle/src/io/github/iltotore/iron/upickle.scala b/upickle/src/io/github/iltotore/iron/upickle.scala new file mode 100644 index 00000000..23733c57 --- /dev/null +++ b/upickle/src/io/github/iltotore/iron/upickle.scala @@ -0,0 +1,49 @@ +package io.github.iltotore.iron + +import _root_.upickle.core.Abort +import _root_.upickle.default.* +import io.github.iltotore.iron.{:|, Constraint, RefinedTypeOps, refineEither} + +/** + * Implicit `Reader`s and `Writer`s for refined types using uPickle. + */ +object upickle: + + /** + * A `Reader` for refined types using uPickle. Decodes to the underlying type then checks the constraint. + * + * @param reader the `Reader` of the underlying type. + * @param constraint the `Constraint` implementation to test the decoded value. + */ + inline given [A, B](using inline reader: Reader[A], inline constraint: Constraint[A, B]): Reader[A :| B] = + reader.map(value => + value.refineEither match { + case Right(refinedValue) => refinedValue + case Left(errorMessage) => throw Abort(errorMessage) + } + ) + + /** + * A `Writer` instance for refined types using uPickle. This is essentially the underlying type `Writer`. + * + * @param writer the `Writer` of the underlying type. + */ + inline given [A, B](using inline writer: Writer[A]): Writer[A :| B] = writer.asInstanceOf[Writer[A :| B]] + + /** + * A uPickle `Reader` based on refined type mirrors. + * + * @param mirror the type mirror for refined types. + * @param ev the underlying `Reader` for the iron type. + */ + inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Reader[mirror.IronType]): Reader[T] = + ev.asInstanceOf[Reader[T]] + + /** + * A uPickle `Writer` based on refined type mirrors. + * + * @param mirror the type mirror for refined types. + * @param ev the underlying `Writer` for the iron type. + */ + inline given[T](using mirror: RefinedTypeOps.Mirror[T], ev: Writer[mirror.IronType]): Writer[T] = + ev.asInstanceOf[Writer[T]] diff --git a/upickle/test/src/io/github/iltotore/iron/uPickleSuite.scala b/upickle/test/src/io/github/iltotore/iron/uPickleSuite.scala new file mode 100644 index 00000000..b105528d --- /dev/null +++ b/upickle/test/src/io/github/iltotore/iron/uPickleSuite.scala @@ -0,0 +1,36 @@ +package io.github.iltotore.iron + +import io.github.iltotore.iron.constraint.numeric.Positive +import io.github.iltotore.iron.upickle.given +import io.github.iltotore.iron.* + +import _root_.upickle.default.* + +import scala.util.Try + +import utest.* + + +object uPickleSuite extends TestSuite: + + import scala.runtime.stdLibPatches.Predef.summon + + val tests: Tests = Tests { + + test("reader") { + test("ironType") { + test("success") - assert(Try(read[Int :| Positive]("10")).isSuccess) + test("failure") - assert(Try(read[Int :| Positive]("-10")).isFailure) + } + } + + test("writer") { + test("ironType") { + val p: Int :| Positive = 10 + test("success") - assert(write(p) == "10") + } + } + + } + +end uPickleSuite From 811761fa607b3cb7a0764a5d1ff3b9dbe17de7ff Mon Sep 17 00:00:00 2001 From: Raphael Parree Date: Wed, 1 Nov 2023 13:45:08 +0100 Subject: [PATCH 2/2] removed ivy deps from upickle test --- build.sc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.sc b/build.sc index b3776bd7..c62e6f78 100644 --- a/build.sc +++ b/build.sc @@ -291,10 +291,6 @@ object upickle extends SubModule { ) object test extends Tests { - def ivyDeps = Agg( - ivy"com.lihaoyi::utest:0.8.1", - ivy"com.lihaoyi::upickle:3.1.3" - ) } object js extends JSCrossModule