Skip to content

Commit

Permalink
feat: Add upickle support (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
rparree authored Nov 1, 2023
1 parent 0871457 commit c8cdbce
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 1 deletion.
18 changes: 17 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -282,6 +282,22 @@ 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 {
}

object js extends JSCrossModule

object native extends NativeCrossModule
}

object circe extends SubModule {

def artifactName = "iron-circe"
Expand Down
49 changes: 49 additions & 0 deletions upickle/src/io/github/iltotore/iron/upickle.scala
Original file line number Diff line number Diff line change
@@ -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]]
36 changes: 36 additions & 0 deletions upickle/test/src/io/github/iltotore/iron/uPickleSuite.scala
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit c8cdbce

Please sign in to comment.