aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach Smith <zach@driver.xyz>2018-03-05 19:57:27 -0800
committerZach Smith <zach@driver.xyz>2018-03-06 22:43:58 -0800
commit93b6eb324feacd2d52afcf8635a3d8e197f01f84 (patch)
tree33d0ea8ed0fb669d3b0b654ac87f9bcc0a40d94e
parent575872b556eeb403147df9fe96b58236e0402050 (diff)
downloaddriver-core-zsmith/id-types.tar.gz
driver-core-zsmith/id-types.tar.bz2
driver-core-zsmith/id-types.zip
Add StringId, LongId, and UuidId types to corezsmith/id-types
-rw-r--r--src/main/scala/xyz/driver/core/core.scala31
-rw-r--r--src/main/scala/xyz/driver/core/database/database.scala24
-rw-r--r--src/main/scala/xyz/driver/core/json.scala34
-rw-r--r--src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala3
-rw-r--r--src/test/scala/xyz/driver/core/CoreTest.scala34
5 files changed, 80 insertions, 46 deletions
diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala
index be19f0f..9e6714d 100644
--- a/src/main/scala/xyz/driver/core/core.scala
+++ b/src/main/scala/xyz/driver/core/core.scala
@@ -54,25 +54,34 @@ package object core {
package core {
- final case class Id[+Tag](value: String) extends AnyVal {
- @inline def length: Int = value.length
- override def toString: String = value
+ sealed trait Id[+Tag] {
+ type Value
+ val value: Value
+ @inline def length: Int = toString.length
+ override def toString: String = value.toString
}
+ final case class StringId[+Tag](value: String) extends Id[Tag] { type Value = String }
+ final case class LongId[+Tag](value: Long) extends Id[Tag] { type Value = Long }
+ final case class UuidId[+Tag](value: java.util.UUID) extends Id[Tag] { type Value = java.util.UUID }
+
@SuppressWarnings(Array("org.wartremover.warts.ImplicitConversion"))
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)
+ def apply[T](value: String): StringId[T] = StringId[T](value)
+
+ implicit def idEqual[T, I[_] <: Id[_]]: Equal[I[T]] = Equal.equalA[I[T]]
+ implicit def idOrdering[T, I[_] <: Id[_]](implicit ord: Ordering[I[T]#Value]): Ordering[I[T]] =
+ Ordering.by[I[T], I[T]#Value](_.value)
- sealed class Mapper[E, R] {
- def apply[T >: E](id: Id[R]): Id[T] = Id[E](id.value)
- def apply[T >: R](id: Id[E])(implicit dummy: DummyImplicit): Id[T] = Id[R](id.value)
+ sealed class Mapper[E, R, I[_] <: Id[_]] {
+ def apply[T >: E](id: I[R]): I[T] = id.asInstanceOf[I[T]]
+ def apply[T >: R](id: I[E])(implicit dummy: DummyImplicit): I[T] = id.asInstanceOf[I[T]]
}
object Mapper {
- def apply[E, R] = new Mapper[E, R]
+ def apply[E, R, I[_] <: Id[_]] = new Mapper[E, R, I]
}
- implicit def convertRE[R, E](id: Id[R])(implicit mapper: Mapper[E, R]): Id[E] = mapper[E](id)
- implicit def convertER[E, R](id: Id[E])(implicit mapper: Mapper[E, R]): Id[R] = mapper[R](id)
+ implicit def convertRE[R, E, I[_] <: Id[_]](id: I[R])(implicit mapper: Mapper[E, R, I]): I[E] = mapper[E](id)
+ implicit def convertER[E, R, I[_] <: Id[_]](id: I[E])(implicit mapper: Mapper[E, R, I]): I[R] = mapper[R](id)
}
final case class Name[+Tag](value: String) extends AnyVal {
diff --git a/src/main/scala/xyz/driver/core/database/database.scala b/src/main/scala/xyz/driver/core/database/database.scala
index ae06517..30adbf6 100644
--- a/src/main/scala/xyz/driver/core/database/database.scala
+++ b/src/main/scala/xyz/driver/core/database/database.scala
@@ -93,30 +93,30 @@ package database {
}
}
- trait IdColumnTypes extends ColumnTypes {
+ trait IdColumnTypes[I[_] <: Id[_]] extends ColumnTypes {
import profile.api._
- implicit def `xyz.driver.core.Id.columnType`[T]: BaseColumnType[Id[T]]
+ implicit def `xyz.driver.core.Id.columnType`[T]: BaseColumnType[I[T]]
}
object IdColumnTypes {
- trait UUID extends IdColumnTypes {
+ trait UUID extends IdColumnTypes[UuidId] {
import profile.api._
override implicit def `xyz.driver.core.Id.columnType`[T] =
MappedColumnType
- .base[Id[T], java.util.UUID](id => java.util.UUID.fromString(id.value), uuid => Id[T](uuid.toString))
+ .base[UuidId[T], java.util.UUID](_.value, UuidId[T])
}
- trait SerialId extends IdColumnTypes {
+ trait SerialId extends IdColumnTypes[LongId] {
import profile.api._
override implicit def `xyz.driver.core.Id.columnType`[T] =
- MappedColumnType.base[Id[T], Long](_.value.toLong, serialId => Id[T](serialId.toString))
+ MappedColumnType.base[LongId[T], Long](_.value, LongId[T])
}
- trait NaturalId extends IdColumnTypes {
+ trait NaturalId extends IdColumnTypes[StringId] {
import profile.api._
override implicit def `xyz.driver.core.Id.columnType`[T] =
- MappedColumnType.base[Id[T], String](_.value, Id[T])
+ MappedColumnType.base[StringId[T], String](_.value, StringId[T])
}
}
@@ -146,11 +146,9 @@ package database {
trait KeyMappers extends ColumnTypes {
import profile.api._
- def uuidKeyMapper[T] =
- MappedColumnType
- .base[Id[T], java.util.UUID](id => java.util.UUID.fromString(id.value), uuid => Id[T](uuid.toString))
- def serialKeyMapper[T] = MappedColumnType.base[Id[T], Long](_.value.toLong, serialId => Id[T](serialId.toString))
- def naturalKeyMapper[T] = MappedColumnType.base[Id[T], String](_.value, Id[T])
+ def uuidKeyMapper[T] = MappedColumnType.base[UuidId[T], java.util.UUID](_.value, UuidId[T])
+ def serialKeyMapper[T] = MappedColumnType.base[LongId[T], Long](_.value, LongId[T])
+ def naturalKeyMapper[T] = MappedColumnType.base[StringId[T], String](_.value, StringId[T])
}
trait DatabaseObject extends ColumnTypes {
diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala
index 02a35fd..c18cadf 100644
--- a/src/main/scala/xyz/driver/core/json.scala
+++ b/src/main/scala/xyz/driver/core/json.scala
@@ -22,18 +22,38 @@ import eu.timepit.refined.collection.NonEmpty
object json {
import DefaultJsonProtocol._
- private def UuidInPath[T]: PathMatcher1[Id[T]] =
- PathMatchers.JavaUUID.map((id: UUID) => Id[T](id.toString.toLowerCase))
+ def UuidInPath[T]: PathMatcher1[UuidId[T]] = PathMatchers.JavaUUID.map(UuidId[T])
+ def LongIdInPath[T]: PathMatcher1[LongId[T]] = PathMatchers.LongNumber.map(LongId[T])
+ def StringIdInPath[T]: PathMatcher1[StringId[T]] = PathMatchers.Segment.map(StringId[T])
- def IdInPath[T]: PathMatcher1[Id[T]] = UuidInPath[T] | new PathMatcher1[Id[T]] {
- def apply(path: Path) = path match {
- case Path.Segment(segment, tail) => Matched(tail, Tuple1(Id[T](segment)))
- case _ => Unmatched
+ def IdInPath[T]: PathMatcher1[Id[T]] = UuidInPath[T] | LongIdInPath[T] | StringIdInPath[T]
+
+ implicit def stringIdFormat[T]: JsonFormat[StringId[T]] = new JsonFormat[StringId[T]] {
+ override def read(json: JsValue): StringId[T] = json match {
+ case JsString(s) => StringId[T](s)
+ case _ => deserializationError(s"Expected string for ID, got $json")
+ }
+ override def write(obj: StringId[T]): JsValue = JsString(obj.value)
+ }
+
+ implicit def uuidIdFormat[T]: JsonFormat[UuidId[T]] = new JsonFormat[UuidId[T]] {
+ override def read(json: JsValue): UuidId[T] = json match {
+ case JsString(s) => UuidId[T](Try(UUID.fromString(s)).getOrElse(deserializationError(s"Invalid UUID format: $s")))
+ case _ => deserializationError(s"Expected UUID string for ID, got $json")
+ }
+ override def write(obj: UuidId[T]): JsValue = JsString(obj.toString)
+ }
+
+ implicit def longIdFormat[T]: JsonFormat[LongId[T]] = new JsonFormat[LongId[T]] {
+ override def read(json: JsValue): LongId[T] = json match {
+ case JsNumber(n) => LongId[T](n.toLong)
+ case _ => deserializationError(s"Expected number for ID, got $json")
}
+ override def write(obj: LongId[T]): JsValue = JsNumber(obj.value)
}
implicit def idFormat[T] = new RootJsonFormat[Id[T]] {
- def write(id: Id[T]) = JsString(id.value)
+ def write(id: Id[T]) = JsString(id.toString)
def read(value: JsValue) = value match {
case JsString(id) if Try(UUID.fromString(id)).isSuccess => Id[T](id.toLowerCase)
diff --git a/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala b/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala
index 66de4ef..38e52bc 100644
--- a/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala
+++ b/src/main/scala/xyz/driver/core/rest/auth/CachedTokenAuthorization.scala
@@ -7,6 +7,7 @@ import java.security.spec.X509EncodedKeySpec
import pdi.jwt.{Jwt, JwtAlgorithm}
import xyz.driver.core.auth.{Permission, User}
import xyz.driver.core.rest.ServiceRequestContext
+import xyz.driver.core.json.idFormat
import scala.concurrent.Future
import scalaz.syntax.std.boolean._
@@ -30,7 +31,7 @@ class CachedTokenAuthorization[U <: User](publicKey: => PublicKey, issuer: Strin
jwtJson = jwt.parseJson.asJsObject
// Ensure jwt is for the currently authenticated user and the correct issuer, otherwise return None
- _ <- jwtJson.fields.get("sub").contains(JsString(user.id.value)).option(())
+ _ <- jwtJson.fields.get("sub").contains(user.id.toJson).option(())
_ <- jwtJson.fields.get("iss").contains(JsString(issuer)).option(())
permissionsMap <- extractPermissionsFromTokenJSON(jwtJson)
diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala
index d280d73..a138332 100644
--- a/src/test/scala/xyz/driver/core/CoreTest.scala
+++ b/src/test/scala/xyz/driver/core/CoreTest.scala
@@ -7,6 +7,9 @@ import org.scalatest.mockito.MockitoSugar
import org.scalatest.{FlatSpec, Matchers}
class CoreTest extends FlatSpec with Matchers with MockitoSugar {
+ // === is already in scope from org.scalactic.TripleEquals
+ def `====`[T: scalaz.Equal](a: T, b: T): Boolean =
+ implicitly[scalaz.Equal[T]].equal(a, b)
"'make' function" should "allow initialization for objects" in {
@@ -29,10 +32,10 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar {
}
"Id" should "have equality and ordering working correctly" in {
-
- (Id[String]("1234213") === Id[String]("1234213")) should be(true)
- (Id[String]("1234213") === Id[String]("213414")) should be(false)
- (Id[String]("213414") === Id[String]("1234213")) should be(false)
+ ====(Id[String]("1234213"), Id[String]("1234213")) should be(true)
+ ====(Id[String]("1234213"), Id[String]("213414")) should be(false)
+ ====(Id[String]("213414"), Id[String]("1234213")) should be(false)
+ ====[Id[String]](StringId[String]("1"), LongId[String](1L)) should be(false)
Seq(Id[String]("4"), Id[String]("3"), Id[String]("2"), Id[String]("1")).sorted should contain
theSameElementsInOrderAs(Seq(Id[String]("1"), Id[String]("2"), Id[String]("3"), Id[String]("4")))
@@ -43,8 +46,11 @@ 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 yz = Id.Mapper[Y, Z]
+ implicit val equalX = scalaz.Equal.equalA[X]
+ implicit val equalY = scalaz.Equal.equalA[Y]
+
+ implicit val xy = Id.Mapper[X, Y, Id]
+ implicit val yz = Id.Mapper[Y, Z, Id]
// Test that implicit conversions work correctly
val x = X(Id("0"))
@@ -52,8 +58,8 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar {
val z = Z(y.id)
val y2 = Y(z.id)
val x2 = X(y2.id)
- (x2 === x) should be(true)
- (y2 === y) should be(true)
+ ====(x2, x) should be(true)
+ ====(y2, y) should be(true)
// Test that type inferrence for explicit conversions work correctly
val yid = y.id
@@ -64,9 +70,9 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar {
"Name" should "have equality and ordering working correctly" in {
- (Name[String]("foo") === Name[String]("foo")) should be(true)
- (Name[String]("foo") === Name[String]("bar")) should be(false)
- (Name[String]("bar") === Name[String]("foo")) should be(false)
+ ====(Name[String]("foo"), Name[String]("foo")) should be(true)
+ ====(Name[String]("foo"), Name[String]("bar")) should be(false)
+ ====(Name[String]("bar"), Name[String]("foo")) should be(false)
Seq(Name[String]("d"), Name[String]("cc"), Name[String]("a"), Name[String]("bbb")).sorted should contain
theSameElementsInOrderAs(Seq(Name[String]("a"), Name[String]("bbb"), Name[String]("cc"), Name[String]("d")))
@@ -77,8 +83,8 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar {
val bla = Revision[String]("85569dab-a3dc-401b-9f95-d6fb4162674b")
val foo = Revision[String]("f54b3558-bdcd-4646-a14b-8beb11f6b7c4")
- (bla === bla) should be(true)
- (bla === foo) should be(false)
- (foo === bla) should be(false)
+ ====(bla, bla) should be(true)
+ ====(bla, foo) should be(false)
+ ====(foo, bla) should be(false)
}
}