Skip to content

Instantly share code, notes, and snippets.

@vil1
Last active December 24, 2018 14:22
Show Gist options
  • Save vil1/7f28126db61ee9d6d0a9407da1b042fa to your computer and use it in GitHub Desktop.
Save vil1/7f28126db61ee9d6d0a9407da1b042fa to your computer and use it in GitHub Desktop.
package scalaz
package schema
package generic
import shapeless._, labelled._
import monocle.Iso
trait AutoSchema extends SchemaModule {
trait SchemaOf[A] extends DepFn0 {
type Out <: Schema[_]
}
def schema[A](implicit A: SchemaOf.Aux[A, A]): Schema[A] = A()
trait LowPrioritySchemaOf {
implicit def schemaOfCaseClass[C, R <: HList, S](
implicit lgen: LabelledGeneric.Aux[C, R],
R: SchemaOf.Aux[R, S],
C: Constructor[S, R]
): SchemaOf.Aux[C, C] = new SchemaOf[C] {
type Out = Schema[C]
def apply: Schema[C] =
record(R(), C.iso.composeIso(Iso[R, C](lgen.from)(lgen.to)))(
new Schema.LabelledProduct[R.Out] {}
)
}
implicit def schemaOfHCons[A, A1, T <: HList, S](
implicit A: SchemaOf.Aux[A, A1],
T: SchemaOf.Aux[T, S]
): SchemaOf.Aux[A :: T, (A1, S)] = new SchemaOf[A :: T] {
type Out = Schema[(A1, S)]
def apply: Schema[(A1, S)] = A() :*: T()
}
implicit def schemaOfSealedTrait[T, R <: Coproduct, S](
implicit lgen: LabelledGeneric.Aux[T, R],
R: SchemaOf.Aux[R, S],
T: Constructor[S, R]
): SchemaOf.Aux[T, T] = new SchemaOf[T] {
type Out = Schema[T]
def apply: Schema[T] =
union(R(), T.iso.composeIso(Iso[R, T](lgen.from)(lgen.to)))(
new Schema.LabelledSum[R.Out] {}
)
}
implicit def schemaOfCCons[A, A1, T <: Coproduct, S](
implicit A: SchemaOf.Aux[A, A1],
T: SchemaOf.Aux[T, S]
): SchemaOf.Aux[A :+: T, A1 \/ S] = new SchemaOf[A :+: T] {
type Out = Schema[A1 \/ S]
def apply: Schema[A1 \/ S] = A() :+: T()
}
implicit def schemaOfBranch[K <: Symbol, V](
implicit K: Witness.Aux[K],
V: SchemaOf.Aux[V, V],
ev: Liskov[String, SumTermId]
): SchemaOf.Aux[FieldType[K, V], V] = new SchemaOf[FieldType[K, V]] {
type Out = Schema[V]
def apply: Schema[V] = ev(K.value.name) -+>: V()
}
}
object SchemaOf extends LowPrioritySchemaOf {
type Aux[A, A0] = SchemaOf[A] { type Out = Schema[A0] }
def apply[T](implicit T: SchemaOf[T]): SchemaOf[T] = T
implicit def schemaOfField[K <: Symbol, V](
implicit K: Witness.Aux[K],
V: SchemaOf.Aux[V, V],
ev: Liskov[String, ProductTermId]
): SchemaOf.Aux[FieldType[K, V], V] = new SchemaOf[FieldType[K, V]] {
type Out = Schema[V]
def apply: Schema[V] = ev(K.value.name) -*>: V()
}
implicit def schemaOfSingletonHList[A, A1](
implicit A: SchemaOf.Aux[A, A1]
): SchemaOf.Aux[A :: HNil, A1] = new SchemaOf[A :: HNil] {
type Out = Schema[A1]
def apply: Schema[A1] = A()
}
implicit def schemaOfSingletonCoproduct[A, A1](
implicit A: SchemaOf.Aux[A, A1]
): SchemaOf.Aux[A :+: CNil, A1] = new SchemaOf[A :+: CNil] {
type Out = Schema[A1]
def apply: Schema[A1] = A()
}
}
trait Constructor[A, A0] {
def iso: Iso[A, A0]
}
trait LowPriorityConstructor {
implicit def constructorOfHCons[K, A, T <: HList, S](
implicit T: Constructor[S, T]
): Constructor[(A, S), FieldType[K, A] :: T] = new Constructor[(A, S), FieldType[K, A] :: T] {
def iso: Iso[(A, S), FieldType[K, A] :: T] = {
val tail = T.iso
Iso[(A, S), FieldType[K, A] :: T](
p => p._1.asInstanceOf[FieldType[K, A]] :: tail.get(p._2)
)(l => (l.head, tail.reverseGet(l.tail)))
}
}
implicit def constructorOfCCons[K, A, T <: Coproduct, S](
implicit T: Constructor[S, T]
): Constructor[A \/ S, FieldType[K, A] :+: T] = new Constructor[A \/ S, FieldType[K, A] :+: T] {
def iso: Iso[A \/ S, FieldType[K, A] :+: T] =
Iso[A \/ S, FieldType[K, A] :+: T] {
case -\/(a) => Inl(a.asInstanceOf[FieldType[K, A]])
case \/-(s) => Inr(T.iso.get(s))
} {
case Inl(a) => -\/(a)
case Inr(s) => \/-(T.iso.reverseGet(s))
}
}
}
object Constructor extends LowPriorityConstructor {
def apply[A, S](implicit C: Constructor[A, S]): Constructor[A, S] = C
implicit def constructorOfSingletonHList[K, A]: Constructor[A, FieldType[K, A] :: HNil] =
new Constructor[A, FieldType[K, A] :: HNil] {
def iso: Iso[A, FieldType[K, A] :: HNil] =
Iso[A, FieldType[K, A] :: HNil](
_.asInstanceOf[FieldType[K, A]] :: HNil
)(_.head.asInstanceOf[A])
}
implicit def constructorOfSingletonCoproduct[K, A]: Constructor[A, FieldType[K, A] :+: CNil] =
new Constructor[A, FieldType[K, A] :+: CNil] {
def iso: Iso[A, FieldType[K, A] :+: CNil] =
Iso[A, FieldType[K, A] :+: CNil](a => Inl(a.asInstanceOf[FieldType[K, A]])) {
case Inl(a) => a
case Inr(_) => throw new Exception("Impossible")
}
}
}
}
object demo {
val autoModule = new JsonModule with AutoSchema {
type Prim[A] = JsonSchema.Prim[A]
type ProductTermId = String
type SumTermId = String
}
import JsonSchema.{ Prim => _, _ }
import autoModule._, SchemaOf._
implicit def schemaOfString: SchemaOf.Aux[String, String] = new SchemaOf[String] {
type Out = Schema[String]
def apply: Schema[String] = prim(JsonSchema.JsonString)
}
implicit def schemaOfBoolean: SchemaOf.Aux[Boolean, Boolean] = new SchemaOf[Boolean] {
type Out = Schema[Boolean]
def apply: Schema[Boolean] = prim(JsonSchema.JsonBool)
}
sealed trait Base
case class Foo(name: String) extends Base
case class Bar(active: Boolean) extends Base
implicit val primToEncoderNT = new (Prim ~> Encoder) {
def apply[A](fa: Prim[A]): Encoder[A] = { a =>
fa match {
case JsonNumber => a.toString
case JsonBool => a.toString
case JsonString => s""""$a""""
case JsonNull => "null"
}
}
}
val baseToJson: Encoder[Base] = schema[Base].to[Encoder]
val json = baseToJson(Foo("Merry Christmas 🎅🎁 !"))
// scalaz.schema.generic.demo.autoModule.JSON = {"Foo":{"name":"Merry Christmas 🎅🎁 !"}}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment