Reputation: 555
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.
Upvotes: 1
Views: 600
Reputation: 46
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)
}
}
}
Upvotes: 3