Last active
February 25, 2023 18:15
-
-
Save todokr/1d4739d27183b49ab9974bdde57d056b to your computer and use it in GitHub Desktop.
refined + newtype + circe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val circeVersion = "0.12.3" | |
lazy val refinedCirceWithNewtype = | |
(project in file("refined_circe_with_newtype")) | |
.settings( | |
scalaVersion := "2.13.1", | |
name := "Refined Circe with Newtype", | |
libraryDependencies ++= Seq( | |
compilerPlugin( | |
"org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full | |
), | |
"io.circe" %% "circe-core" % circeVersion, | |
"io.circe" %% "circe-generic" % circeVersion, | |
"io.circe" %% "circe-parser" % circeVersion, | |
"io.circe" %% "circe-refined" % circeVersion, | |
"com.softwaremill.common" %% "tagging" % "2.2.1", | |
"eu.timepit" %% "refined" % "0.9.13", | |
"io.estatico" %% "newtype" % "0.4.3", | |
scalaTest | |
), | |
scalacOptions ++= Seq( | |
"-Xfatal-warnings", // New lines for each options | |
"-deprecation", | |
"-unchecked", | |
"-language:implicitConversions", | |
"-language:higherKinds", | |
"-language:existentials", | |
"-language:postfixOps", | |
"-Ywarn-dead-code", | |
"-Ywarn-numeric-widen", | |
"-Xfatal-warnings", | |
"-deprecation", | |
"-Xlint:-unused,_", | |
"-deprecation", | |
"-Ymacro-annotations", | |
"-Xmaxerrs", | |
"200", | |
) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Models._ | |
import eu.timepit.refined.api._ | |
import eu.timepit.refined.boolean.{And, Or} | |
import eu.timepit.refined.collection._ | |
import eu.timepit.refined.numeric._ | |
import eu.timepit.refined.string._ | |
import io.circe._ | |
import io.circe.generic.semiauto._ | |
import io.circe.parser._ | |
import io.estatico.newtype.Coercible | |
import io.estatico.newtype.macros.newtype | |
import io.estatico.newtype.ops._ | |
object Models { | |
type AccountIdRule = | |
MinSize[3] And MaxSize[10] And StartsWith["@"] // AccountIdは 3 ~ 10文字かつ先頭が@ | |
type AgeRule = NonNegative // Ageは自然数 | |
type IpAddressRule = IPv4 Or IPv6 // IpAddressはv4かv6 | |
type AccountIdString = String Refined AccountIdRule | |
type AgeInt = Int Refined AgeRule | |
type IpAddressString = String Refined IpAddressRule | |
@newtype case class AccountId(value: AccountIdString) | |
@newtype case class Age(value: AgeInt) | |
@newtype case class IpAddress(value: IpAddressString) | |
case class Account(accountId: AccountId, age: Age, ipAddress: IpAddress) | |
} | |
trait CoercibleCodecs { | |
implicit def coercibleDecoder[A: Coercible[B, *], B: Decoder]: Decoder[A] = | |
Decoder[B].map(_.coerce[A]) | |
implicit def coercibleEncoder[A: Coercible[B, *], B: Encoder]: Encoder[A] = | |
Encoder[B].contramap(_.repr.asInstanceOf[B]) | |
implicit def coercibleKeyDecoder[A: Coercible[B, *], B: KeyDecoder] | |
: KeyDecoder[A] = | |
KeyDecoder[B].map(_.coerce[A]) | |
implicit def coercibleKeyEncoder[A: Coercible[B, *], B: KeyEncoder] | |
: KeyEncoder[A] = | |
KeyEncoder[B].contramap[A](_.repr.asInstanceOf[B]) | |
} | |
object Codecs extends CoercibleCodecs { | |
import io.circe.refined._ // unusedに見えるが実は使われている | |
implicit val messageEncoder: Encoder[Account] = deriveEncoder[Account] | |
implicit val messageDecoder: Decoder[Account] = deriveDecoder[Account] | |
} | |
object Example extends App { | |
import Codecs._ | |
Iterator | |
.continually(Console.in.readLine()) | |
.takeWhile(_ != ":quit") | |
.foreach { rawMessage => | |
decode[Account](rawMessage) match { | |
case Left(value) => println(value) | |
case Right(msg) => | |
println(s"accountId: ${msg.accountId}, age: ${msg.age}") | |
// これをimportしておくと F[T, P]をTにunwrapしてくれる | |
import eu.timepit.refined.auto.autoUnwrap | |
val ip: String = | |
msg.ipAddress.value // autoUnwrapがないと msg.body.value.value | |
println(s"from: $ip") | |
} | |
println("*" * 30) | |
println() | |
import eu.timepit.refined.auto._ | |
val accountId = AccountId("#todokr") | |
val age = Age(-1) | |
println(accountId) | |
println(age) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment