From f0b89933f97c73c14484643c14438d23e1723334 Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Sun, 18 Dec 2016 16:22:14 -0500 Subject: add SameId for typechecked conversion between Ids --- src/main/scala/xyz/driver/core/core.scala | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala index 8ae9122..ce6be34 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/src/main/scala/xyz/driver/core/core.scala @@ -1,6 +1,7 @@ package xyz.driver import scalaz.Equal +import scala.annotation.implicitNotFound package object core { @@ -24,6 +25,24 @@ package object core { package core { + @implicitNotFound("No evidence that ${A} has the same Id as ${B}") + sealed trait SameId[A, B] { + def convert(id: Id[A]): Id[B] = Id[B](id.value) + } + + object SameId extends LowPrioritySameIdImplicits { + def apply[A, B] = new SameId[A, B] {} + + implicit def reflexive[A]: A ~ A = SameId[A, A] + implicit def symmetric[A, B](implicit ab: A ~ B): B ~ A = SameId[B, A] + } + + trait LowPrioritySameIdImplicits { + protected type ~[A, B] = SameId[A, B] + + implicit def transitive[A, B, C](implicit ab: A ~ B, bc: B ~ C): A ~ C = SameId[A, C] + } + final case class Id[+Tag](value: String) extends AnyVal { @inline def length: Int = value.length override def toString: String = value @@ -32,6 +51,12 @@ package core { object Id { implicit def idEqual[T]: Equal[Id[T]] = Equal.equal[Id[T]](_ == _) implicit def idOrdering[T]: Ordering[Id[T]] = Ordering.by[Id[T], String](_.value) + + implicit class InvariantIdOps[Tag](id: Id[Tag]) { + def asId[T](implicit eq: SameId[Tag, T]): Id[T] = eq.convert(id) + } + + def sameId[A, B] = SameId[A, B] } final case class Name[+Tag](value: String) extends AnyVal { -- cgit v1.2.3 From f1c50e2cbe757926df7a94284eb0246c66394779 Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Sun, 18 Dec 2016 16:33:03 -0500 Subject: add documentation for sameId --- src/main/scala/xyz/driver/core/core.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala index ce6be34..1a6bdd7 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/src/main/scala/xyz/driver/core/core.scala @@ -25,6 +25,13 @@ package object core { package core { + /** + * Evidence that Id[A] can be safely converted to Id[B]. + * e.g. `implicit val CaseId = Id.sameId[Case, CasesRow]` + * if `CaseId` is in scope, we can use either of: + * `casesRowId.asId[Case]` or `caseId.asId[CasesRow]` + * Override convert for custom Id conversions. + */ @implicitNotFound("No evidence that ${A} has the same Id as ${B}") sealed trait SameId[A, B] { def convert(id: Id[A]): Id[B] = Id[B](id.value) -- cgit v1.2.3 From 415c88db0447bc94ae1624f28dcc4cc9b2d4be63 Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Sun, 18 Dec 2016 17:03:16 -0500 Subject: scope SameId to Id --- src/main/scala/xyz/driver/core/core.scala | 52 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala index 1a6bdd7..c88c998 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/src/main/scala/xyz/driver/core/core.scala @@ -25,31 +25,6 @@ package object core { package core { - /** - * Evidence that Id[A] can be safely converted to Id[B]. - * e.g. `implicit val CaseId = Id.sameId[Case, CasesRow]` - * if `CaseId` is in scope, we can use either of: - * `casesRowId.asId[Case]` or `caseId.asId[CasesRow]` - * Override convert for custom Id conversions. - */ - @implicitNotFound("No evidence that ${A} has the same Id as ${B}") - sealed trait SameId[A, B] { - def convert(id: Id[A]): Id[B] = Id[B](id.value) - } - - object SameId extends LowPrioritySameIdImplicits { - def apply[A, B] = new SameId[A, B] {} - - implicit def reflexive[A]: A ~ A = SameId[A, A] - implicit def symmetric[A, B](implicit ab: A ~ B): B ~ A = SameId[B, A] - } - - trait LowPrioritySameIdImplicits { - protected type ~[A, B] = SameId[A, B] - - implicit def transitive[A, B, C](implicit ab: A ~ B, bc: B ~ C): A ~ C = SameId[A, C] - } - final case class Id[+Tag](value: String) extends AnyVal { @inline def length: Int = value.length override def toString: String = value @@ -59,11 +34,34 @@ package core { implicit def idEqual[T]: Equal[Id[T]] = Equal.equal[Id[T]](_ == _) implicit def idOrdering[T]: Ordering[Id[T]] = Ordering.by[Id[T], String](_.value) + /** + * Evidence that Id[A] can be safely converted to Id[B]. + * e.g. `implicit val CaseId = Id.SameId[Case, CasesRow]` + * if `CaseId` is in scope, we can use either of: + * `casesRowId.asId[Case]` or `caseId.asId[CasesRow]` + * Override convert for custom Id conversions. + */ + @implicitNotFound("No evidence that ${A} has the same Id as ${B}") + sealed trait SameId[A, B] { + def convert(id: Id[A]): Id[B] = Id[B](id.value) + } + + object SameId extends LowPrioritySameIdImplicits { + def apply[A, B] = new SameId[A, B] {} + + implicit def reflexive[A]: A ~ A = SameId[A, A] + implicit def symmetric[A, B](implicit ab: A ~ B): B ~ A = SameId[B, A] + } + + trait LowPrioritySameIdImplicits { + protected type ~[A, B] = SameId[A, B] + + implicit def transitive[A, B, C](implicit ab: A ~ B, bc: B ~ C): A ~ C = SameId[A, C] + } + implicit class InvariantIdOps[Tag](id: Id[Tag]) { def asId[T](implicit eq: SameId[Tag, T]): Id[T] = eq.convert(id) } - - def sameId[A, B] = SameId[A, B] } final case class Name[+Tag](value: String) extends AnyVal { -- cgit v1.2.3 From e3c4666f718797defab28dc90a9c1912f5750223 Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Sun, 18 Dec 2016 19:28:45 -0500 Subject: remove reflexive/transitive SameId implicits --- src/main/scala/xyz/driver/core/core.scala | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala index c88c998..d1128b6 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/src/main/scala/xyz/driver/core/core.scala @@ -46,17 +46,9 @@ package core { def convert(id: Id[A]): Id[B] = Id[B](id.value) } - object SameId extends LowPrioritySameIdImplicits { + object SameId { def apply[A, B] = new SameId[A, B] {} - - implicit def reflexive[A]: A ~ A = SameId[A, A] - implicit def symmetric[A, B](implicit ab: A ~ B): B ~ A = SameId[B, A] - } - - trait LowPrioritySameIdImplicits { - protected type ~[A, B] = SameId[A, B] - - implicit def transitive[A, B, C](implicit ab: A ~ B, bc: B ~ C): A ~ C = SameId[A, C] + implicit def symmetric[A, B](implicit ab: SameId[A, B]): SameId[B, A] = SameId[B, A] } implicit class InvariantIdOps[Tag](id: Id[Tag]) { -- cgit v1.2.3 From ea49093016f12989233bbb402f49b37b230e4c40 Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Sun, 18 Dec 2016 19:32:50 -0500 Subject: add tests for typesafe id conversion --- src/test/scala/xyz/driver/core/CoreTest.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala index 3eb9eaa..1ccd707 100644 --- a/src/test/scala/xyz/driver/core/CoreTest.scala +++ b/src/test/scala/xyz/driver/core/CoreTest.scala @@ -39,6 +39,23 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar { theSameElementsInOrderAs(Seq(Id[String]("1"), Id[String]("2"), Id[String]("3"), Id[String]("4"))) } + it should "have type-safe conversions" in { + final case class X(id: Id[X]) + final case class Y(id: Id[Y]) + final case class Z(id: Id[Z]) + + implicit val xy = Id.SameId[X,Y] + implicit val yz = Id.SameId[Y,Z] + + val x = X(Id("0")) + val y = Y(x.id.asId[Y]) + val z = Z(y.id.asId[Z]) + val y2 = Y(z.id.asId[Y]) + val x2 = X(z.id.asId[Y].asId[X]) + x2 === x + y2 === y + } + "Name" should "have equality and ordering working correctly" in { (Name[String]("foo") === Name[String]("foo")) should be(true) -- cgit v1.2.3 From aca1f9c10b89499b073046d9b50fa455b675e20e Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Sun, 18 Dec 2016 19:37:53 -0500 Subject: use implicit mappers for typed Id conversion --- src/main/scala/xyz/driver/core/core.scala | 22 ++-------------------- src/test/scala/xyz/driver/core/CoreTest.scala | 15 +++++++++------ 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala index d1128b6..783150a 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/src/main/scala/xyz/driver/core/core.scala @@ -1,7 +1,6 @@ package xyz.driver import scalaz.Equal -import scala.annotation.implicitNotFound package object core { @@ -34,25 +33,8 @@ package core { implicit def idEqual[T]: Equal[Id[T]] = Equal.equal[Id[T]](_ == _) implicit def idOrdering[T]: Ordering[Id[T]] = Ordering.by[Id[T], String](_.value) - /** - * Evidence that Id[A] can be safely converted to Id[B]. - * e.g. `implicit val CaseId = Id.SameId[Case, CasesRow]` - * if `CaseId` is in scope, we can use either of: - * `casesRowId.asId[Case]` or `caseId.asId[CasesRow]` - * Override convert for custom Id conversions. - */ - @implicitNotFound("No evidence that ${A} has the same Id as ${B}") - sealed trait SameId[A, B] { - def convert(id: Id[A]): Id[B] = Id[B](id.value) - } - - object SameId { - def apply[A, B] = new SameId[A, B] {} - implicit def symmetric[A, B](implicit ab: SameId[A, B]): SameId[B, A] = SameId[B, A] - } - - implicit class InvariantIdOps[Tag](id: Id[Tag]) { - def asId[T](implicit eq: SameId[Tag, T]): Id[T] = eq.convert(id) + object Mapper { + def apply[A, B]: (Id[A] => Id[B]) = (id: Id[A]) => Id[B](id.value) } } diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala index 1ccd707..72b6bc6 100644 --- a/src/test/scala/xyz/driver/core/CoreTest.scala +++ b/src/test/scala/xyz/driver/core/CoreTest.scala @@ -44,14 +44,17 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar { final case class Y(id: Id[Y]) final case class Z(id: Id[Z]) - implicit val xy = Id.SameId[X,Y] - implicit val yz = Id.SameId[Y,Z] + implicit val xy = Id.Mapper[X,Y] + implicit val yx = Id.Mapper[Y,X] + implicit val yz = Id.Mapper[Y,Z] + implicit val zy = Id.Mapper[Z,Y] val x = X(Id("0")) - val y = Y(x.id.asId[Y]) - val z = Z(y.id.asId[Z]) - val y2 = Y(z.id.asId[Y]) - val x2 = X(z.id.asId[Y].asId[X]) + val y = Y(x.id) + val z = Z(y.id) + val y2 = Y(z.id) + val x2 = X(y2.id) + x2 === x y2 === y } -- cgit v1.2.3 From b66ac9deca6875f0a9757f6263d0170312be5bd8 Mon Sep 17 00:00:00 2001 From: Stewart Stewart Date: Mon, 19 Dec 2016 19:34:54 -0500 Subject: test:scalafmt and add assertions --- src/test/scala/xyz/driver/core/CoreTest.scala | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala index 72b6bc6..da9fd9a 100644 --- a/src/test/scala/xyz/driver/core/CoreTest.scala +++ b/src/test/scala/xyz/driver/core/CoreTest.scala @@ -44,19 +44,20 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar { final case class Y(id: Id[Y]) final case class Z(id: Id[Z]) - implicit val xy = Id.Mapper[X,Y] - implicit val yx = Id.Mapper[Y,X] - implicit val yz = Id.Mapper[Y,Z] - implicit val zy = Id.Mapper[Z,Y] - - val x = X(Id("0")) - val y = Y(x.id) - val z = Z(y.id) + implicit val xy = Id.Mapper[X, Y] + implicit val yx = Id.Mapper[Y, X] + implicit val yz = Id.Mapper[Y, Z] + implicit val zy = Id.Mapper[Z, Y] + + // The real test is that the following statements compile: + val x = X(Id("0")) + val y = Y(x.id) + val z = Z(y.id) val y2 = Y(z.id) val x2 = X(y2.id) - x2 === x - y2 === y + (x2 === x) should be(true) + (y2 === y) should be(true) } "Name" should "have equality and ordering working correctly" in { -- cgit v1.2.3