Last active
May 25, 2017 07:23
-
-
Save mikla/dd0b277146606e62c19cb3d210e7b07e to your computer and use it in GitHub Desktop.
Type class for extracting field with UserId type from case class.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil, Inl, Inr} | |
| import simulacrum.typeclass | |
| import language.implicitConversions | |
| case class UserId(value: UUID) extends AnyVal | |
| /** | |
| * Type class for extracting field with UserId type from case class. | |
| * | |
| * @tparam T | |
| */ | |
| @typeclass trait AffectsUser[T] { | |
| def userId(t: T): Option[UserId] | |
| } | |
| object AffectsUser extends AffectedUserLowerPriorityImplicits { | |
| def instance[A](f: A => Option[UserId]): AffectsUser[A] = new AffectsUser[A] { | |
| override def userId(t: A): Option[UserId] = f(t) | |
| } | |
| implicit val hNil = instance[HNil](_ => None) | |
| implicit def hConsUserId[T <: HList](implicit A: AffectsUser[T]) = | |
| instance[UserId :: T](t => Some(t.head)) | |
| implicit def genAffectsUser[T, Repr](implicit G: Generic.Aux[T, Repr], A: AffectsUser[Repr]) = | |
| instance[T](t => A.userId(G.to(t))) | |
| implicit def affectedUserCNil: AffectsUser[CNil] = new AffectsUser[CNil] { | |
| override def userId(t: CNil): Option[UserId] = t.impossible | |
| } | |
| implicit def affectedUserCCons[H, T <: Coproduct]( | |
| implicit sh: AffectsUser[H], | |
| st: AffectsUser[T] | |
| ): AffectsUser[H :+: T] = instance[H :+: T] { | |
| case Inl(l) => sh.userId(l) | |
| case Inr(r) => st.userId(r) | |
| } | |
| } | |
| trait AffectedUserLowerPriorityImplicits { | |
| import AffectsUser.instance | |
| implicit def hCons[H, T <: HList](implicit A: AffectsUser[T]) = | |
| instance[H :: T](t => A.userId(t.tail)) | |
| } | |
| class AffectsUserCoproductSpec extends FunSuite with Matchers { | |
| test("AffectsUser should support sealed trait type as a type parameter") { | |
| val userId = UserId.generate | |
| val event = EventWithUserId("event-name", userId) | |
| def eventUserId[T <: Event](event: T)(implicit A: AffectsUser[T]) = A.userId(event) | |
| eventUserId(event) should be (Some(userId)) | |
| } | |
| } | |
| class AffectsUserSpec extends FunSuite with Matchers { | |
| test("AffectsUser should return UserId field from case class") { | |
| val userId = UserId.generate | |
| val event = EventWithUserId("event-name", userId) | |
| implicitly[AffectsUser[EventWithUserId]].userId(event) should be (Some(userId)) | |
| } | |
| test("AffectsUser should return None if UserId field is not present in case class") { | |
| val event = EventWithoutUserId("event-name") | |
| implicitly[AffectsUser[EventWithoutUserId]].userId(event) should be (None) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment