diff --git a/build.sbt b/build.sbt index 70e9b4442..17b630e13 100644 --- a/build.sbt +++ b/build.sbt @@ -161,7 +161,7 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Full) .in(file("modules/tests")) - .dependsOn(core, circe) + .dependsOn(core, circe, refined) .enablePlugins(AutomateHeaderPlugin, NoPublishPlugin) .settings(commonSettings) .settings( @@ -175,6 +175,7 @@ lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) "org.typelevel" %%% "cats-laws" % "2.10.0", "org.typelevel" %%% "discipline-munit" % "2.0.0-M3", "org.typelevel" %%% "cats-time" % "0.5.1", + "eu.timepit" %%% "refined-cats" % "0.10.3", ), testFrameworks += new TestFramework("munit.Framework"), testOptions += { diff --git a/modules/refined/src/main/scala/codec/NumericCodecs.scala b/modules/refined/src/main/scala/codec/NumericCodecs.scala deleted file mode 100644 index 92eb94977..000000000 --- a/modules/refined/src/main/scala/codec/NumericCodecs.scala +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2018-2021 by Rob Norris -// This software is licensed under the MIT License (MIT). -// For more information see LICENSE or https://opensource.org/licenses/MIT - -package skunk.refined.codec - -import eu.timepit.refined.boolean.And -import eu.timepit.refined.api.Validate -import skunk.Codec -import skunk.data.Type -import eu.timepit.refined._ -import eu.timepit.refined.api.Refined - -trait Scale[N] -trait Precision[N] - -trait NumericCodecs { - - def validateScale[N <: Int](n: Int): Validate.Plain[BigDecimal, Scale[N]] = - Validate.fromPredicate[BigDecimal, Scale[N]]( - _.scale <= n, d => s"($d has scale ≤ $n)", new Scale[N] {}) - - def validatePrecision[N <: Int](n: Int): Validate.Plain[BigDecimal, Precision[N]] = - Validate.fromPredicate[BigDecimal, Precision[N]]( - _.precision <= n, d => s"($d has precision ≤ $n)", new Precision[N] {}) - - implicit class NumericOps(num: Codec[BigDecimal]) { - def apply[S <: Int, P <: Int](precision: P, scale: S): Codec[BigDecimal Refined (Precision[P] And Scale[S])] = { - implicit val __s: Validate.Plain[BigDecimal, Scale[S]] = validateScale(scale) - implicit val __p: Validate.Plain[BigDecimal, Precision[P]] = validatePrecision(precision) - Codec.simple( - bd => bd.toString, - st => refineV[Precision[P] And Scale[S]](BigDecimal(st)), - Type.numeric - ) - } - } - - skunk.codec.numeric.numeric(3, 4) - -} - -object numeric extends NumericCodecs \ No newline at end of file diff --git a/modules/refined/src/main/scala/skunk/refined/codec/RefTypeCodecs.scala b/modules/refined/src/main/scala/skunk/refined/codec/RefTypeCodecs.scala new file mode 100644 index 000000000..97599501c --- /dev/null +++ b/modules/refined/src/main/scala/skunk/refined/codec/RefTypeCodecs.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.refined.codec + +import skunk.{ Codec, Encoder, Decoder } +import eu.timepit.refined.api.{ RefType, Validate } + +trait RefTypeCodecs { + def refTypeCodec[T, P, F[_,_]](codecT: Codec[T])( + implicit validate: Validate[T, P], refType: RefType[F]): Codec[F[T, P]] = + codecT.eimap[F[T,P]]( + refType.refine[P](_)(validate))( + refType.unwrap + ) + + def refTypeEncoder[T, P, F[_,_]](writeT: Encoder[T])(implicit refType: RefType[F]): Encoder[F[T,P]] = + writeT.contramap[F[T,P]](refType.unwrap) + + def refTypeDecoder[T, P, F[_,_]](readT: Decoder[T])( + implicit validate: Validate[T, P], refType: RefType[F]): Decoder[F[T,P]] = + readT.emap[F[T,P]](refType.refine[P](_)(validate)) +} + +object refType extends RefTypeCodecs diff --git a/modules/refined/src/main/scala/skunk/refined/codec/RefinedCodecs.scala b/modules/refined/src/main/scala/skunk/refined/codec/RefinedCodecs.scala new file mode 100644 index 000000000..6d4219b7e --- /dev/null +++ b/modules/refined/src/main/scala/skunk/refined/codec/RefinedCodecs.scala @@ -0,0 +1,23 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.refined.codec + +import skunk.{ Codec, Encoder, Decoder } +import eu.timepit.refined.api.{ Refined, Validate } + +trait RefinedCodecs { + + def refinedCodec[T, P](codecT: Codec[T])(implicit v: Validate[T, P]): Codec[Refined[T, P]] = + refType.refTypeCodec[T, P, Refined](codecT) + + def refinedDecoder[T, P](decoderT: Decoder[T])(implicit v: Validate[T, P]): Decoder[Refined[T, P]] = + refType.refTypeDecoder[T, P, Refined](decoderT) + + def refinedEncoder[T, P](encoderT: Encoder[T]): Encoder[Refined[T, P]] = + refType.refTypeEncoder[T, P, Refined](encoderT) + +} + +object refined extends RefinedCodecs \ No newline at end of file diff --git a/modules/refined/src/main/scala/skunk/refined/codec/Syntax.scala b/modules/refined/src/main/scala/skunk/refined/codec/Syntax.scala new file mode 100644 index 000000000..3784fe15f --- /dev/null +++ b/modules/refined/src/main/scala/skunk/refined/codec/Syntax.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package skunk.refined.codec + +import skunk.{ Codec, Encoder, Decoder } +import eu.timepit.refined.api.{ Refined, Validate } + + +object syntax { + implicit class RefineCodecOps[T](val c: Codec[T]) { + def refine[P](implicit v: Validate[T, P]): Codec[Refined[T, P]] = + refined.refinedCodec(c) + } + + implicit class RefineEncoderOps[T](val c: Encoder[T]) { + def refine[P]: Encoder[Refined[T, P]] = + refined.refinedEncoder(c) + } + + implicit class RefineDecoderOps[T](val c: Decoder[T]) { + def refine[P](implicit v: Validate[T, P]): Decoder[Refined[T, P]] = + refined.refinedDecoder(c) + } +} diff --git a/modules/tests/shared/src/test/scala/codec/RefinedCodecTest.scala b/modules/tests/shared/src/test/scala/codec/RefinedCodecTest.scala new file mode 100644 index 000000000..97dd3ac91 --- /dev/null +++ b/modules/tests/shared/src/test/scala/codec/RefinedCodecTest.scala @@ -0,0 +1,21 @@ +// Copyright (c) 2018-2021 by Rob Norris +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package tests.codec + +import skunk.codec.all._ +import skunk.refined.codec.syntax._ +import eu.timepit.refined.collection.NonEmpty +import eu.timepit.refined.types.string.NonEmptyString +import eu.timepit.refined.types.numeric.{PosInt, NonNaNDouble} +import eu.timepit.refined.numeric.{Positive, NonNaN} +import eu.timepit.refined.cats._ + +class RefinedCodecTest extends CodecTest { + + roundtripTest(varchar.refine[NonEmpty])(NonEmptyString.unsafeFrom("foo")) + roundtripTest(int4.refine[Positive])(PosInt.unsafeFrom(42)) + roundtripTest(float8.refine[NonNaN])(NonNaNDouble.unsafeFrom(2.0)) + +}