Skip to content

Instantly share code, notes, and snippets.

@Fristi
Created November 28, 2018 07:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Fristi/b2378c0aa321b652bc96a96872295d56 to your computer and use it in GitHub Desktop.
Save Fristi/b2378c0aa321b652bc96a96872295d56 to your computer and use it in GitHub Desktop.
specs2 `Diffable` magnolia derivation
import magnolia.{CaseClass, Magnolia, SealedTrait}
import org.specs2.matcher.describe._
import scala.language.experimental.macros
object DiffableDerivation {
/** binds the Magnolia macro to this derivation object */
implicit def genDiffable[T]: Diffable[T] = macro Magnolia.gen[T]
/** type constructor for new instances of the typeclass */
type Typeclass[T] = Diffable[T]
/** defines how new Arbitrary typeclasses for nested case classes should be constructed */
def combine[T](ctx: CaseClass[Diffable, T]): Diffable[T] =
new Diffable[T] {
override def diff(actual: T, expected: T): ComparisonResult = {
val res = ctx.parameters.map { param =>
val result = param.typeclass.diff(param.dereference(actual), param.dereference(expected))
CaseClassPropertyComparison(param.label, result, result.identical)
}
if (res.forall(_.identical)) CaseClassIdentical(ctx.typeName.short)
else CaseClassDifferent(ctx.typeName.short, res)
}
}
/** Given a sealed trait, picks a random Arbitrary instance for one of the elements*/
def dispatch[T](ctx: SealedTrait[Diffable, T]): Diffable[T] = new Diffable[T] {
override def diff(actual: T, expected: T): ComparisonResult = ctx.dispatch(actual) { sub =>
if (sub.cast.isDefinedAt(expected)) sub.typeclass.diff(sub.cast(actual), sub.cast(expected))
else OtherDifferent(actual, expected)
}
}
}
import org.specs2.matcher.Matchers
import org.specs2.mutable.Specification
import DiffableDerivation._
import org.specs2.matcher.describe._
import scala.collection.mutable.ArrayBuffer
class DiffableDerivationSpec extends Specification with Matchers {
"Diffable type class derivation" >> {
"should derive correctly for product type" >> {
"identical" >> {
genDiffable[Address].diff(Address("a", 2), Address("a", 2)) must beEqualTo(CaseClassIdentical("Address"))
}
"different" >> {
genDiffable[Address].diff(Address("a", 2), Address("a", 4)) must beEqualTo(CaseClassDifferent("Address",ArrayBuffer(CaseClassPropertyComparison("street",PrimitiveIdentical("a"),true), CaseClassPropertyComparison("houseNumber",PrimitiveDifference(2,4),false))))
}
}
"should derive correctly for nested types" >> {
"identical" >> {
genDiffable[Person].diff(Person("Mark", 1337, Address("a", 2)), Person("Mark", 1337, Address("a", 2))) must beEqualTo(CaseClassIdentical("Person"))
}
"different" >> {
genDiffable[Person].diff(Person("Mark", 1337, Address("a", 2)), Person("Mark", 1338, Address("a", 3))) must beEqualTo(
CaseClassDifferent("Person",
ArrayBuffer(
CaseClassPropertyComparison("name",PrimitiveIdentical("Mark"),true),
CaseClassPropertyComparison("age",PrimitiveDifference(1337,1338),false),
CaseClassPropertyComparison("address", CaseClassDifferent("Address",
ArrayBuffer(
CaseClassPropertyComparison("street",PrimitiveIdentical("a"),true),
CaseClassPropertyComparison("houseNumber",PrimitiveDifference(2,3),false)
)), false
)
)))
}
}
}
}
case class Person(name: String, age: Int, address: Address)
case class Address(street: String, houseNumber: Int)
object Refined {
implicit def diffableRefinedType[S, P](implicit S: Diffable[S]): Diffable[Refined[S, P]] = new Diffable[Refined[S, P]] {
override def diff(actual: Refined[S, P], expected: Refined[S, P]): ComparisonResult = S.diff(actual.value, expected.value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment