// 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