Mapping on shapeless HList - scala

I would like to create a generic version of the following code:
I have a case class and an encryption function
case class Cat(name: String, age: Int, color: String)
val encrypt : String => String = _.hashCode.toString // as an example
val encryptableFields = Seq("color")
I have the Poly1 which will do the mapping in my HList
import shapeless._
import labelled._
import record._
trait enc extends Poly1 {
implicit def defaultEncrypt[K,V] = at[(K, V)] { case (k,v) =>field[K](v)}
}
object pol extends enc {
implicit def stringEncrypt[K <: Symbol] = at[(K, String)] { case (k,v) => field[K](if(encryptableFields contains k.name) encrypt(v) else v)}
}
When I'm using it it works as expected:
val cat = Cat("name", 1, "black")
val lgCat = LabelledGeneric[Cat]
val la = lgCat.to(cat)
val a = la.fields.map(pol)
lgCat.from(a)
// Cat("name", 1, "93818879")
Because it works I was thinking about creating it a generic way and encapsulate the functionality and a type class like:
trait Encryptor[T] {
val fields: Seq[String]
def encryptFields(source: T, encrypt: String => String): T
}
object Encryptor {
def forClass[A <: Product](f: Seq[String]) = new Encryptor[A] {
val fields: Seq[String] = f
override def encryptFields(source:A, encrypt: String => String): A = {
object pol extends enc {
implicit def stringEncrypt[K <: Symbol] = at[(K, String)] { case (k, v) => field[K](if (f contains k.name) encrypt(v) else v) }
}
val gen = LabelledGeneric[A]
val hList = gen.to(source)
val updated = hList.fields.map(pol)
gen.from(updated)
}
}
}
With this implementation I get the following compile time error:
Error:could not find implicit value for parameter lgen: shapeless.LabelledGeneric[A]
val gen = LabelledGeneric[A]
Tried to solve it with passing the LabelledGeneric[A] implicitly raises more questions.
def forClass[A <: Product, R <: HList](f: Seq[String])(implicit gen: implicit gen: LabelledGeneric.Aux[A, R]) = new Encryptor[A] { ... }
Complaining about Error:(46, 27) could not find implicit value for parameter fields: shapeless.ops.record.Fields[gen.Repr]; val updated = hList.fields.map(pol)
When trying to pass one:
def forClass[A <: Product, R <: HList, FOut <: HList](f: Seq[String])(
implicit gen: LabelledGeneric.Aux[A, R], fields: Fields.Aux[R, FOut])
I have the same issue.
I wonder how to overcome this issue.

I came up with another approach.
Instead of doing everything at once you can break it down to smaller pieces and operate on the HList with a different approach.
Let's create a type class for the inner representation:
trait Encryptor[T] {
def encryptFields(source: T, encrypt: String => String, fields: Seq[String]): T
}
In your example you have only Int and String fields so I'll stick to that.
import shapeless._
import labelled._
object Encryptor {
def apply[A](implicit enc: Encryptor[A]): Encryptor[A] = enc
implicit val stringEncryptor: Encryptor[String] = new Encryptor[String] {
override def encryptFields(source: String, encrypt: String => String, fields: Seq[String]) = encrypt(source)
}
implicit val intEncryptor: Encryptor[Int] = new Encryptor[Int] {
override def encryptFields(source: Int, encrypt: String => String, fields: Seq[String]) = source
}
implicit val hnilEncryptor: Encryptor[HNil] = new Encryptor[HNil] {
override def encryptFields(source: HNil, encrypt: String => String, fields: Seq[String]) = HNil
}
implicit def hlistEncryptor[A, K <: Symbol, H, T <: HList](
implicit
witness: Witness.Aux[K],
hEncryptor: Lazy[Encryptor[H]],
tEncryptor: Encryptor[T]
): Encryptor[FieldType[K, H] :: T] = new Encryptor[FieldType[K, H] :: T] {
val fieldName: String = witness.value.name
override def encryptFields(source: FieldType[K, H] :: T, encrypt: String => String, fields: Seq[String]) = {
val tail = tEncryptor.encryptFields(source.tail, encrypt, fields)
val head = if (fields contains fieldName) field[K](hEncryptor.value.encryptFields(source.head, encrypt, fields))
else source.head
head :: tail
}
}
import shapeless.LabelledGeneric
implicit def genericObjectEncryptor[A, H <: HList](
implicit
generic: LabelledGeneric.Aux[A, H],
hEncryptor: Lazy[Encryptor[H]]
): Encryptor[A] = new Encryptor[A] {
override def encryptFields(source: A, encrypt: String => String, fields: Seq[String]) = {
generic.from(hEncryptor.value.encryptFields(generic.to(source), encrypt, fields))
}
}
}
Because in your example you apply the encrypt function only on the String fields it is only used in the stringEncrytor instance. The Encryptor for the HList checks if Symbol's name of the head of HList is in the provided fields if so it applies the the encypt otherwise it skips it.
Using LabelledGeneric for making it work on any case class
To provide the same interface:
trait PayloadEncryptor[T] {
def encrypt(source: T, encrypt: String => String): T
}
object PayloadEncryptor {
def forClass[T](fieldNames: String*)(implicit encryptor: Encryptor[T]): PayloadEncryptor[T] = new PayloadEncryptor[T] {
override def encrypt(source: T, encrypt: String => String): T = {
encryptor.encryptFields(source, encrypt, fieldNames)
}
}
}

Related

How to make a typeclass works with an heterogenous List in scala

Given the following typeclass and some instances for common types
trait Encoder[A] {
def encode(a: A): String
}
object Encoder {
implicit val stringEncoder = new Encoder[String] {
override def encode(a: String): String = a
}
implicit val intEncoder = new Encoder[Int] {
override def encode(a: Int): String = String.valueOf(a)
}
implicit def listEncoder[A: Encoder] =
new Encoder[List[A]] {
override def encode(a: List[A]): String = {
val encoder = implicitly[Encoder[A]]
a.map(encoder.encode).mkString(",")
}
}
}
Is there a way to make it work
Encoder.listEncoder.encode(List("a", 1))
If you define an instance for Any
object Encoder {
implicit val anyEncoder = new Encoder[Any] {
override def encode(a: Any): String = a.toString
}
//instances for String, Int, List[A]
}
then
Encoder.listEncoder[Any].encode(List("a", 1))
will work.
You have to specify type parameter here explicitly (listEncoder[Any]) because that's how type inference work in scala. Indeed, after the type of argument of encode is inferred
Encoder.listEncoder[???].encode(List("a", 1))
// ^^^^^^^^^^^^
// List[Any]
it's too late to come back to infer the type parameter of listEncoder.
If you define a delegator function
def encode[A](a: A)(implicit encoder: Encoder[A]): String = encoder.encode(a)
or syntax
implicit class EncoderOps[A](a: A)(implicit encoder: Encoder[A]) {
def encode: String = encoder.encode(a)
}
// or
// implicit class EncoderOps[A](a: A) {
// def encode(implicit encoder: Encoder[A]): String = encoder.encode(a)
// }
then you don't have to specify type parameter explicitly
encode("a")
encode(1)
encode(List("a", 1))
"a".encode
1.encode
List("a", 1).encode
By the way, List("a", 1) is not a heterogenous list, it's an ordinary homogenous list with element type Any. A heterogenous list would be
val l: String :: Int :: HNil = "a" :: 1 :: HNil
You can try a magnet instead of type class
trait Magnet {
def encode(): String
}
object Magnet {
implicit def fromInt(a: Int): Magnet = new Magnet {
override def encode(): String = String.valueOf(a)
}
implicit def fromString(a: String): Magnet = new Magnet {
override def encode(): String = a
}
}
def encode(m: Magnet): String = m.encode()
encode("a")
encode(1)
List[Magnet]("a", 1).map(_.encode())
or HList instead of List[A]
sealed trait HList
case class ::[+H, +T <: HList](head: H, tail: T) extends HList
case object HNil extends HList
type HNil = HNil.type
implicit class HListOps[L <: HList](l: L) {
def ::[A](a: A): A :: L = new ::(a, l)
}
trait Encoder[A] {
def encode(a: A): String
}
object Encoder {
implicit val stringEncoder: Encoder[String] = new Encoder[String] {
override def encode(a: String): String = a
}
implicit val intEncoder: Encoder[Int] = new Encoder[Int] {
override def encode(a: Int): String = String.valueOf(a)
}
implicit val hnilEncoder: Encoder[HNil] = new Encoder[HNil] {
override def encode(a: HNil): String = ""
}
implicit def hconsEncoder[H, T <: HList](implicit
hEncoder: Encoder[H],
tEncoder: Encoder[T]
): Encoder[H :: T] = new Encoder[H :: T] {
override def encode(a: H :: T): String =
s"${hEncoder.encode(a.head)},${tEncoder.encode(a.tail)}"
}
}
def encode[A](a: A)(implicit encoder: Encoder[A]): String = encoder.encode(a)
implicit class EncoderOps[A](a: A)(implicit encoder: Encoder[A]) {
def encode: String = encoder.encode(a)
}
val l: String :: Int :: HNil = "a" :: 1 :: HNil
encode(l)
l.encode

How to extract types from a tuple that implements a typeclass

Function a can receive single argument or a tuple, these arguments need to be members of typeclass StringIdentifiable
How to extract and decompose tuple type into types that also have instances of the typeclass
#typeclass trait StringIdentifiable[M] {
def identify(id: M): String
}
def a[K: StringIdentifiable] (k:K){
k match{
case (k1) =>
implicitly[StringIdentifiable[K]].identify(k1)
case (k1,k2) =>
implicitly[StringIdentifiable[k1.type]].identify(k1)
implicitly[StringIdentifiable[k2.type]].identify(k2)
}
I get error in the second match:
Could not find an instance of StringIdentifiable for k1.type
k1.type, k2.type are singleton types. Try
#typeclass trait StringIdentifiable[M] {
def identify(id: M): String
}
object StringIdentifiable {
implicit def one[K]: StringIdentifiable[K] = ???
implicit def two[K1: StringIdentifiable, K2: StringIdentifiable]: StringIdentifiable[(K1, K2)] = {
new StringIdentifiable[(K1, K2)] {
override def identify(id: (K1, K2)): String = id match {
case (k1,k2) =>
implicitly[StringIdentifiable[K1]].identify(k1)
implicitly[StringIdentifiable[K2]].identify(k2)
???
}
}
}
}
def a[K: StringIdentifiable](k:K): String = implicitly[StringIdentifiable[K]].identify(k)
You can do this with shapeless. For instance:
import shapeless._, ops.hlist._
object MyPoly extends Poly2 {
implicit def foo[A] = at[A, StringIdentifiable[A]]( (a, f) => f.identify(a) )
}
def a[K: StringIdentifiable, L <: HList, O <: HList](k: K)(
implicit
gen: Generic.Aux[K, L], // decompose K into HList L
lift: LiftAll.Aux[StringIdentifiable, L, O], // find an instance of StringIdentifiable for every element of L
zip: ZipWith[L, O, MyPoly.type] // zip L with its typeclass instances and map the results with the polymorphic function MyPoly
): String :: zip.Out = {
val l = gen.to(k)
val o = lift.instances
implicitly[StringIdentifiable[K]].identify(k) :: zip(l, o)
}
implicit def id1[A,B]: StringIdentifiable[(A, B)] = _ => "1"
implicit val id2: StringIdentifiable[String] = _ => "2"
implicit val id3: StringIdentifiable[Int] = _ => "3"
a(("foo", 42)) // 1 :: 2 :: 3 :: HNil
A full solution to your problem (IIUC) probably consists of using shapeless to automatically generate StringIdentifiable instances for all tuples.
trait StringIdentifiable[M] {
def identify(id: M): String
}
object StringIdentifiable {
object MyPoly extends Poly2 {
implicit def foo[A] = at[A, StringIdentifiable[A]]( (a, f) => f.identify(a) )
}
implicit def mkSI[K, L <: HList, O <: HList](
implicit
tup: IsTuple[K],
gen: Generic.Aux[K, L],
lift: LiftAll.Aux[StringIdentifiable, L, O],
zip: ZipWith[L, O, MyPoly.type]
): StringIdentifiable[K] = {
val o = lift.instances
k => {
val l = gen.to(k)
zip(l, o).mkString("(", ", ", ")")
}
}
}

LabelledGenerid.Aux implicit not available when using generic type in function body

I'm trying this and it works:
`
case class Foo(name: String)
class Morphic(map: Map[String, Any]) {
def add(k: String, v: Any) = {
new Morphic((map + (k -> v)))
}
def to[T](): T = {
def toClass[A]: ToCase[A] = new ToCase[A] // This is class to convert from Map to case class
val res = toClass[Foo].from(map).get // <-- problem is here - cannot use T
res.asInstanceOf[T]
}
}
object testApp extends App {
var m = new Morphic(Map[String, Any]())
var m1 = m.add("name", "john")
println(m1.to[Foo])
}
I should use T instead of Foo in val res = toClass[T].from(map).get but it doesn't compile saying implicit is missing
toClass[T].from is a function creating a given type of case class from Map
How do I make that implicit (and possibly others on which .from relies) available?
I tried def to[T, H <: HList]()(implicit gen: LabelledGeneric.Aux[A, H]) = ... but then I need to specify both types when calling the .to and I can't figure out what to specify for H
Thanks
You can transform a Map into HList, and then the HList into T:
import shapeless.{::, HList, HNil, LabelledGeneric, Witness}
import shapeless.labelled._
case class Foo(name: String)
trait MapToHList[L <: HList] {
def apply(map: Map[String, Any]): Option[L]
}
object MapToHList {
implicit object hNilMapToHList extends MapToHList[HNil] {
override def apply(map: Map[String, Any]): Option[HNil] = Some(HNil)
}
implicit def hConsMapToHList[K <: Symbol, V, T <: HList](implicit
mapToHList: MapToHList[T],
witness: Witness.Aux[K]
): MapToHList[FieldType[K, V] :: T] =
new MapToHList[FieldType[K, V] :: T] {
override def apply(map: Map[String, Any]): Option[FieldType[K, V] :: T] = {
val str = witness.value.toString.tail
for {
v <- map.get(str)
t <- mapToHList(map)
} yield field[K](v.asInstanceOf[V]) :: t
}
}
}
trait ToCase[A] {
def from(map: Map[String, Any]): Option[A]
}
object ToCase {
implicit def mkToCase[A, L <: HList](implicit
gen: LabelledGeneric.Aux[A, L],
mapToHList: MapToHList[L]
): ToCase[A] =
new ToCase[A] {
override def from(map: Map[String, Any]): Option[A] = mapToHList(map).map(gen.from)
}
}
class Morphic(map: Map[String, Any]) {
def add(k: String, v: Any) = {
new Morphic((map + (k -> v)))
}
def to[T](implicit toCase: ToCase[T]): T = toCase.from(map).get
}
object testApp extends App {
var m = new Morphic(Map[String, Any]())
var m1 = m.add("name", "john")
println(m1.to[Foo]) // Foo(john)
}
I tried
def to[T, H <: HList]()(implicit gen: LabelledGeneric.Aux[A, H]) ...
but then I need to specify both types when calling the .to
and I can't figure out what to specify for H
You can call it as m1.to[Foo, FieldType[Witness.`'name`.T, String] :: HNil]() or m1.to[Foo, Record.`'name -> String`.T]().

Generically encode arbitrary case class into AWS Sdk DynamoDb Item

do you think what i did make sense? Is there a better way do encode a case class into Item? e.g. i'm not happy with ignoring in some cases an input param!
import shapeless.labelled.FieldType
import shapeless.{::, DepFn2, HList, HNil, LabelledGeneric, Witness}
import scala.collection.mutable
// mock of sdk item
class Item(val map: mutable.Map[String, Any] = mutable.Map[String, Any]()) {
def getString(attrName: String): String = map.get(attrName).get.asInstanceOf[String]
def getInt(attrName: String): Int = map.get(attrName).get.asInstanceOf[Int]
def getBoolean(attrName: String): Boolean = map.get(attrName).get.asInstanceOf[Boolean]
// def getMap(attrName: String): Map[String, String] = Map("attrName" -> "attrValue")
def setString(attrName: String, value: String): Unit = map.put(attrName, value)
def setInt(attrName: String, value: Int): Unit = map.put(attrName, value)
def setBoolean(attrName: String, value: Boolean): Unit = map.put(attrName, value)
override def toString() = map.toString()
}
trait ItemEncoder[A] extends DepFn2[String, A] {
type Out = Item
}
object ItemEncoder {
def apply[A](implicit encoder: ItemEncoder[A]): ItemEncoder[A] = encoder
def instance[A](f: (String, A) => Item): ItemEncoder[A] =
new ItemEncoder[A] {
override def apply(attrName: String, value: A): Out = f(attrName, value)
}
}
implicit val stringEncoder: ItemEncoder[String] =
ItemEncoder.instance { (attrName, value) =>
val item = new Item()
item.setString(attrName, value)
item
}
implicit val intEncoder: ItemEncoder[Int] =
ItemEncoder.instance { (attrName, value) =>
val item = new Item()
item.setInt(attrName, value)
item
}
implicit val booleanEncoder: ItemEncoder[Boolean] =
ItemEncoder.instance { (attrName, value) =>
val item = new Item()
item.setBoolean(attrName, value)
item
}
implicit val hnilEncoder: ItemEncoder[HNil] =
ItemEncoder.instance((attrName, value) => new Item())
def merge(i1: Item, i2: Item): Item = new Item(i1.map ++ i2.map)
implicit def hlistEncoder[K <: Symbol, L, H, T <: HList](
implicit
witness: Witness.Aux[K],
hEncoder: ItemEncoder[H],
tEncoder: ItemEncoder[T]
): ItemEncoder[FieldType[K, H] :: T] = {
ItemEncoder.instance { (_, value) =>
val attrName = witness.value.name
merge(hEncoder.apply(attrName, value.head), tEncoder.apply(attrName, value.tail))
}
}
implicit def genericEncoder[A, R](
implicit
generic: LabelledGeneric.Aux[A, R],
itemEncoder: ItemEncoder[R]
): ItemEncoder[A] =
ItemEncoder.instance { (attrName, value) =>
itemEncoder.apply(attrName, generic.to(value))
}
case class Person(name: String, age: Int, married: Boolean, manager: Boolean)
case class IceCream(name: String, subName: String, price: Int)
val genericPerson = LabelledGeneric[Person].to(Person("bob", 37, true, true))
def encode[A](toEncode: A)(implicit itemEncoder: ItemEncoder[A]) =
itemEncoder("", toEncode)
Maybe it will be better to use ToMap or something like this, and the convert it to Item
After getting deeper into the topic i managed to implement ItemEncoder that converts a case class with arbitrary nesting into Item like this:
import com.amazonaws.services.dynamodbv2.document.Item
import shapeless.labelled.FieldType
import shapeless.{::, HList, HNil, LabelledGeneric, Witness, _}
trait ItemEncoder[A] {
def encode(value: A): Item
}
object ItemEncoder {
def apply[A](implicit encoder: ItemEncoder[A]): ItemEncoder[A] = encoder
def instance[A](f: A => Item): ItemEncoder[A] =
new ItemEncoder[A] {
override def encode(value: A): Item = f(value)
}
implicit def stringEncoder[K <: Symbol, V <: String](
implicit witness: Witness.Aux[K]
): ItemEncoder[FieldType[K, V]] =
instance { value =>
val item = new Item
item.withString(witness.value.name, value)
item
}
implicit def intEncoder[K <: Symbol, V <: Int](
implicit witness: Witness.Aux[K]
): ItemEncoder[FieldType[K, V]] =
instance { value =>
val item = new Item
item.withInt(witness.value.name, value)
item
}
implicit def booleanEncoder[K <: Symbol, V <: Boolean](
implicit witness: Witness.Aux[K]
): ItemEncoder[FieldType[K, V]] =
instance { value =>
val item = new Item
item.withBoolean(witness.value.name, value)
item
}
// K is key, A is value, R is HList representation of A
implicit def nestedClassEncoder[K <: Symbol, A, R](
implicit
witness: Witness.Aux[K],
generic: LabelledGeneric.Aux[A, R],
encoder: ItemEncoder[R]
): ItemEncoder[FieldType[K, A]] =
instance { value =>
val i = encoder.encode(generic.to(value))
val item = new Item
val m = new java.util.HashMap[String, Any]()
item.withMap(witness.value.name, i.asMap())
item
}
import cats.Monoid
implicit val itemMonoid: Monoid[Item] = new Monoid[Item] {
override def empty: Item = new Item()
override def combine(x: Item, y: Item): Item = {
val m = x.asMap
m.putAll(y.asMap())
Item.fromMap(m)
}
}
implicit val hnilEncoder: ItemEncoder[HNil] =
instance(_ => new Item())
implicit def hlistEncoder[H, T <: HList](
implicit
hEncoder: Lazy[ItemEncoder[H]],
tEncoder: ItemEncoder[T],
monoid: Monoid[Item]
): ItemEncoder[H :: T] =
instance { value =>
// println("hlist enc")
val itemX = hEncoder.value.encode(value.head)
val itemY = tEncoder.encode(value.tail)
monoid.combine(itemX, itemY)
}
implicit def genericEncoder[A, R](
implicit
generic: LabelledGeneric.Aux[A, R],
itemEncoder: Lazy[ItemEncoder[R]]
): ItemEncoder[A] =
instance { value =>
// println("gen enc")
itemEncoder.value.encode(generic.to(value))
}
def encode[A](toEncode: A)(implicit itemEncoder: ItemEncoder[A]) =
itemEncoder.encode(toEncode)
}
Current implementation is a bit simplified. So it contains ItemEncoder implementation only for such a primitive types as String, Int and Boolean. But other primitive types can be easily added by using the present ones as example.
You can find complete implementation with QuickCheck tests on Git

Convert a List[String] to a case class using Shapeless

I was wondering if anyone could provide some insight on a problem I'm having. I've made a gist with some code and explanation of my problem: https://gist.github.com/tbrown1979/9993f07c8f4fa2786c83
Basically I'm trying to make something that will allow me to convert List[String] to a case class. I've made a Reader that will allow me to do so, but I've run into the issue where a Reader defined for a case class can't contain a reader for a separate case class.
Looking at the 'non-working example' below - I encounter an issue where, when reading, I don't know how many items to pull out of the list. With Bar, which holds a Test, I would need to pull 2 elements out (because Test has two parameters). Is there a way for me to know the amount of fields a case class has just from its type? Is there a better way to do this?
Here is an example of how to use the Reader. I've included a non-working example as well.
////Working Example////
case class Foo(a: Int, s: String)
object Foo {
implicit val FooReader : Reader[Foo] =
Reader[Int :: String :: HNil].map(Generic[Foo].from _)
}
val read: ValidationNel[String, Foo] = Reader.read[Foo](List("12","text"))
println(read)//Success(Foo(12, "text"))
///////////////////////////
////Non-working Example////
case class Test(a: Int, b: String)
object Test {
implicit val TestReader: Reader[Test] =
Reader[Int :: String :: HNil].map(Generic[Test].from _)
}
case class Bar(c: Test)
object Bar {
implicit val BarReader: Reader[Bar] =
Reader[Test :: HNil].map(Generic[Bar].from _)
}
val barRead = Reader.read[Bar](List("21", "someString"))
println(barRead) //Failure(NonEmptyList("Invalid String: List()", "Exepected empty, but contained value"))
//////////////////////////
Something like this works for me (modification of this)
object ShapelessStringToTypeConverters {
import cats._, implicits._, data.ValidatedNel
import mouse._, string._, option._
import shapeless._, labelled._
private type Result[A] = ValidatedNel[ParseFailure, A]
case class ParseFailure(error: String)
trait Convert[V] {
def parse(input: String): Result[V]
}
object Convert {
def to[V](input: String)(implicit C: Convert[V]): Result[V] =
C.parse(input)
def instance[V](body: String => Result[V]): Convert[V] = new Convert[V] {
def parse(input: String): Result[V] = body(input)
}
implicit def booleans: Convert[Boolean] =
Convert.instance(
s =>
s.parseBooleanValidated
.leftMap(e => ParseFailure(s"Not a Boolean ${e.getMessage}"))
.toValidatedNel)
implicit def ints: Convert[Int] =
Convert.instance(
s =>
s.parseIntValidated
.leftMap(e => ParseFailure(s"Not an Int ${e.getMessage}"))
.toValidatedNel)
implicit def longs: Convert[Long] =
Convert.instance(
s =>
s.parseLongValidated
.leftMap(e => ParseFailure(s"Not an Long ${e.getMessage}"))
.toValidatedNel)
implicit def doubles: Convert[Double] =
Convert.instance(
s =>
s.parseDoubleValidated
.leftMap(e => ParseFailure(s"Not an Double ${e.getMessage}"))
.toValidatedNel)
implicit def strings: Convert[String] = Convert.instance(s => s.validNel)
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sealed trait SchemaMap[A] {
def readFrom(input: Map[String, String]): ValidatedNel[ParseFailure, A]
}
object SchemaMap {
def of[A](implicit s: SchemaMap[A]): SchemaMap[A] = s
private def instance[A](body: Map[String, String] => Result[A]): SchemaMap[A] = new SchemaMap[A] {
def readFrom(input: Map[String, String]): Result[A] =
body(input)
}
implicit val noOp: SchemaMap[HNil] =
SchemaMap.instance(_ => HNil.validNel)
implicit def parsing[K <: Symbol, V: Convert, T <: HList](implicit key: Witness.Aux[K], next: SchemaMap[T]): SchemaMap[FieldType[K, V] :: T] =
SchemaMap.instance { input =>
val fieldName = key.value.name
val parsedField = input
.get(fieldName)
.cata(entry => Convert.to[V](entry), ParseFailure(s"$fieldName is missing").invalidNel)
.map(f => field[K](f))
(parsedField, next.readFrom(input)).mapN(_ :: _)
}
implicit def classes[A, R <: HList](implicit repr: LabelledGeneric.Aux[A, R], schema: SchemaMap[R]): SchemaMap[A] =
SchemaMap.instance { input =>
schema.readFrom(input).map(x => repr.from(x))
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sealed trait SchemaList[A] {
def readFrom(input: List[String]): ValidatedNel[ParseFailure, A]
}
object SchemaList {
def of[A](implicit s: SchemaList[A]): SchemaList[A] = s
private def instance[A](body: List[String] => Result[A]): SchemaList[A] = new SchemaList[A] {
def readFrom(input: List[String]): Result[A] = body(input)
}
implicit val noOp: SchemaList[HNil] =
SchemaList.instance(_ => HNil.validNel)
implicit def parsing[K <: Symbol, V: Convert, T <: HList](implicit key: Witness.Aux[K], next: SchemaList[T]): SchemaList[FieldType[K, V] :: T] =
SchemaList.instance { input =>
val fieldName = key.value.name
val parsedField = input
.headOption
.cata(entry => Convert.to[V](entry), ParseFailure(s"$fieldName is missing").invalidNel)
.map(f => field[K](f))
(parsedField, next.readFrom(input.tail)).mapN(_ :: _)
}
implicit def classes[A, R <: HList](implicit repr: LabelledGeneric.Aux[A, R], schema: SchemaList[R]): SchemaList[A] =
SchemaList.instance { input =>
schema.readFrom(input).map(x => repr.from(x))
}
}
}
/*
case class Foo(a: String, b: Int, c: Boolean)
def m: Map[String, String] = Map("a" -> "hello", "c" -> "true", "b" -> "100")
def e: Map[String, String] = Map("c" -> "true", "b" -> "a100")
val result = SchemaMap.of[Foo].readFrom(m)
val lst = List("145164983", "0.01862523", "16.11681596", "21:38:57", "bid")
case class Trade0(tid: Long, price: Double, amount: Double, time: String, tpe: String)
val result2 = SchemaList.of[Trade0].readFrom(lst)
*/