Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// allows converting one class to another by providing missing fields
object convert {
@annotation.implicitNotFound("""
You have not provided enough arguments to convert from ${In} to ${Out}.
${Args}
""")
trait Convertible[Args, In, Out] {
def apply(args: Args, in: In): Out
}
object Convertible {
// This is a hack to allow 'to' to be a single-parameter method. The implicit
// search in Converter for the Convertible instance drives the lookup for the other dependenices
implicit def makeConvertible[Args <: HList, In, RIn <: HList, Out, ROut <: HList, MR <: HList, IR <: HList](
implicit
ingen: LabelledGeneric.Aux[In, RIn],
outgen: LabelledGeneric.Aux[Out, ROut],
merger: Merger.Aux[RIn, Args, MR],
intersection: Intersection.Aux[MR, ROut, IR],
align: Align[IR, ROut]
): Convertible[Args, In, Out] = new Convertible[Args, In, Out] {
def apply(args: Args, in: In) = {
outgen.from(align(intersection(merger(ingen.to(in), args))))
}
}
}
// this builder pattern is needed since RecordArgs blows up if a type parameter is added to it,
// preventing the nicer syntax of convert[T](id =...)
class ConvertibleBuilder[Args <: HList, In](args: Args, in: In) {
def to[Out](implicit c: Convertible[Args, In, Out]) = c(args, in)
}
object syntax {
implicit class ConvertOps[T <: Product](t: T) {
object convert extends RecordArgs {
object to {
def apply[U](implicit c: Convertible[HNil, T, U]) = c(HNil, t)
}
def applyRecord[R <: HList](rec: R) = new ConvertibleBuilder(rec, t)
}
}
}
}
import convert.syntax._
case class NewUser(name: String, age: Int)
case class User(id: Int, name: String, age: Int)
val x: NewUser = NewUser(name = "abc", age = 23)
val y: User = x.convert(id = 1).to[User]
val z: NewUser = y.convert.to[NewUser]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.