aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKseniya Tomskikh <ktomskikh@driver.xyz>2018-10-08 17:56:10 +0800
committerKseniya Tomskikh <ktomskikh@driver.xyz>2018-10-08 17:56:10 +0800
commitde97eebf217f9e934decdb80bc840b9e1365a890 (patch)
treea94b26b9b4ff07163fc451f82f785a8357521371
parent60ad2abd17a50c8bd73bfe75084984b4de27bd79 (diff)
downloaddriver-core-de97eebf217f9e934decdb80bc840b9e1365a890.tar.gz
driver-core-de97eebf217f9e934decdb80bc840b9e1365a890.tar.bz2
driver-core-de97eebf217f9e934decdb80bc840b9e1365a890.zip
Created GenericId and typized id classes
-rw-r--r--src/main/scala/xyz/driver/core/core.scala52
-rw-r--r--src/main/scala/xyz/driver/core/database/database.scala37
-rw-r--r--src/main/scala/xyz/driver/core/generators.scala2
-rw-r--r--src/main/scala/xyz/driver/core/json.scala18
-rw-r--r--src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala6
-rw-r--r--src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala10
-rw-r--r--src/test/scala/xyz/driver/core/JsonTest.scala27
7 files changed, 151 insertions, 1 deletions
diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala
index 2ab4e88..11c1ffe 100644
--- a/src/main/scala/xyz/driver/core/core.scala
+++ b/src/main/scala/xyz/driver/core/core.scala
@@ -64,7 +64,15 @@ package object core {
package core {
- final case class Id[+Tag](value: String) extends AnyVal {
+ import java.util.UUID
+
+ sealed trait GenericId[+Tag, IdType] extends Any {
+ def value: IdType
+ def length: Int
+ def toString: String
+ }
+
+ final case class Id[+Tag](value: String) extends AnyVal with GenericId[Tag, String] {
@inline def length: Int = value.length
override def toString: String = value
}
@@ -85,6 +93,48 @@ package core {
implicit def convertER[E, R](id: Id[E])(implicit mapper: Mapper[E, R]): Id[R] = mapper[R](id)
}
+ final case class UuidId[+Tag](value: UUID) extends AnyVal with GenericId[Tag, UUID] {
+ @inline def length: Int = value.toString.length
+ override def toString: String = value.toString
+ }
+
+ @SuppressWarnings(Array("org.wartremover.warts.ImplicitConversion"))
+ object UuidId {
+ implicit def idEqual[T]: Equal[UuidId[T]] = Equal.equal[UuidId[T]](_ == _)
+ implicit def idOrdering[T]: Ordering[UuidId[T]] = Ordering.by[UuidId[T], UUID](_.value)
+
+ sealed class Mapper[E, R] {
+ def apply[T >: E](id: UuidId[R]): UuidId[T] = UuidId[E](id.value)
+ def apply[T >: R](id: UuidId[E])(implicit dummy: DummyImplicit): UuidId[T] = UuidId[R](id.value)
+ }
+ object Mapper {
+ def apply[E, R] = new Mapper[E, R]
+ }
+ implicit def convertRE[R, E](id: UuidId[R])(implicit mapper: Mapper[E, R]): UuidId[E] = mapper[E](id)
+ implicit def convertER[E, R](id: UuidId[E])(implicit mapper: Mapper[E, R]): UuidId[R] = mapper[R](id)
+ }
+
+ final case class NumericId[+Tag](value: Long) extends AnyVal with GenericId[Tag, Long] {
+ @inline def length: Int = value.toString.length
+ override def toString: String = value.toString
+ }
+
+ @SuppressWarnings(Array("org.wartremover.warts.ImplicitConversion"))
+ object NumericId {
+ implicit def idEqual[T]: Equal[NumericId[T]] = Equal.equal[NumericId[T]](_ == _)
+ implicit def idOrdering[T]: Ordering[NumericId[T]] = Ordering.by[NumericId[T], Long](_.value)
+
+ sealed class Mapper[E, R] {
+ def apply[T >: E](id: NumericId[R]): NumericId[T] = NumericId[E](id.value)
+ def apply[T >: R](id: NumericId[E])(implicit dummy: DummyImplicit): NumericId[T] = NumericId[R](id.value)
+ }
+ object Mapper {
+ def apply[E, R] = new Mapper[E, R]
+ }
+ implicit def convertRE[R, E](id: NumericId[R])(implicit mapper: Mapper[E, R]): NumericId[E] = mapper[E](id)
+ implicit def convertER[E, R](id: NumericId[E])(implicit mapper: Mapper[E, R]): NumericId[R] = mapper[R](id)
+ }
+
final case class Name[+Tag](value: String) extends AnyVal {
@inline def length: Int = value.length
override def toString: String = value
diff --git a/src/main/scala/xyz/driver/core/database/database.scala b/src/main/scala/xyz/driver/core/database/database.scala
index bd20b54..f3630ff 100644
--- a/src/main/scala/xyz/driver/core/database/database.scala
+++ b/src/main/scala/xyz/driver/core/database/database.scala
@@ -126,6 +126,33 @@ package database {
}
}
+ trait GenericIdColumnTypes[IdType] extends ColumnTypes {
+ import profile.api._
+ implicit def `xyz.driver.core.GenericId.columnType`[T]: BaseColumnType[GenericId[T, IdType]]
+ }
+
+ object GenericIdColumnTypes {
+ trait UUID extends GenericIdColumnTypes[java.util.UUID] {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.GenericId.columnType`[T]: BaseColumnType[GenericId[T, java.util.UUID]] =
+ MappedColumnType
+ .base[GenericId[T, java.util.UUID], java.util.UUID](id => id.value, uuid => UuidId[T](uuid))
+ }
+ trait SerialId extends GenericIdColumnTypes[Long] {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.GenericId.columnType`[T]: BaseColumnType[GenericId[T, Long]] =
+ MappedColumnType.base[GenericId[T, Long], Long](_.value, serialId => NumericId[T](serialId))
+ }
+ trait NaturalId extends GenericIdColumnTypes[String] {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.GenericId.columnType`[T]: BaseColumnType[GenericId[T, String]] =
+ MappedColumnType.base[GenericId[T, String], String](_.value, Id[T])
+ }
+ }
+
trait TimestampColumnTypes extends ColumnTypes {
import profile.api._
implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time]
@@ -166,6 +193,16 @@ package database {
def naturalKeyMapper[T] = MappedColumnType.base[Id[T], String](_.value, Id[T])
}
+ trait GenericKeyMappers extends ColumnTypes {
+ import profile.api._
+
+ def uuidKeyMapper[T] =
+ MappedColumnType
+ .base[UuidId[T], java.util.UUID](id => id.value, uuid => UuidId[T](uuid))
+ def serialKeyMapper[T] = MappedColumnType.base[NumericId[T], Long](_.value, serialId => NumericId[T](serialId))
+ def naturalKeyMapper[T] = MappedColumnType.base[Id[T], String](_.value, Id[T])
+ }
+
trait DatabaseObject extends ColumnTypes {
def createTables(): Future[Unit]
def disconnect(): Unit
diff --git a/src/main/scala/xyz/driver/core/generators.scala b/src/main/scala/xyz/driver/core/generators.scala
index d00b6dd..0a4a7ab 100644
--- a/src/main/scala/xyz/driver/core/generators.scala
+++ b/src/main/scala/xyz/driver/core/generators.scala
@@ -65,6 +65,8 @@ object generators {
def nextNumericId[T](maxValue: Int): Id[T] = Id[T](nextInt(maxValue).toString)
+ def nextUuidId[T](): UuidId[T] = UuidId[T](nextUuid())
+
def nextName[T](maxLength: Int = DefaultMaxLength): Name[T] = Name[T](nextString(maxLength))
def nextNonEmptyName[T](maxLength: Int = DefaultMaxLength): NonEmptyName[T] =
diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala
index 4daf127..48011e4 100644
--- a/src/main/scala/xyz/driver/core/json.scala
+++ b/src/main/scala/xyz/driver/core/json.scala
@@ -37,6 +37,24 @@ object json extends PathMatchers with Unmarshallers {
}
}
+ implicit def uuidIdFormat[T]: RootJsonFormat[UuidId[T]] = new RootJsonFormat[UuidId[T]] {
+ def write(id: UuidId[T]) = JsString(id.toString)
+
+ def read(value: JsValue): UuidId[T] = value match {
+ case JsString(id) if Try(UUID.fromString(id)).isSuccess => UuidId[T](UUID.fromString(id))
+ case _ => throw DeserializationException("Id expects UUID")
+ }
+ }
+
+ implicit def numericIdFormat[T]: RootJsonFormat[NumericId[T]] = new RootJsonFormat[NumericId[T]] {
+ def write(id: NumericId[T]) = JsString(id.toString)
+
+ def read(value: JsValue): NumericId[T] = value match {
+ case JsString(id) if Try(id.toLong).isSuccess => NumericId[T](id.toLong)
+ case _ => throw DeserializationException("Id expects number")
+ }
+ }
+
implicit def taggedFormat[F, T](implicit underlying: JsonFormat[F], convert: F => F @@ T = null): JsonFormat[F @@ T] =
new JsonFormat[F @@ T] {
import tagging._
diff --git a/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala b/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala
index 183ad9a..8ba184f 100644
--- a/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala
+++ b/src/main/scala/xyz/driver/core/rest/directives/PathMatchers.scala
@@ -27,6 +27,12 @@ trait PathMatchers {
}
}
+ def UuidIdInPath[T]: PathMatcher1[UuidId[T]] =
+ AkkaPathMatchers.JavaUUID.map((id: UUID) => UuidId[T](id))
+
+ def NumericIdInPath[T]: PathMatcher1[NumericId[T]] =
+ AkkaPathMatchers.LongNumber.map((id: Long) => NumericId[T](id))
+
def NameInPath[T]: PathMatcher1[Name[T]] = new PathMatcher1[Name[T]] {
def apply(path: Path) = path match {
case Path.Segment(segment, tail) => Matched(tail, Tuple1(Name[T](segment)))
diff --git a/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala b/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala
index 6c45d15..93a9a52 100644
--- a/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala
+++ b/src/main/scala/xyz/driver/core/rest/directives/Unmarshallers.scala
@@ -16,6 +16,16 @@ trait Unmarshallers {
Id[A](UUID.fromString(str).toString)
}
+ implicit def uuidIdUnmarshaller[A]: Unmarshaller[String, UuidId[A]] =
+ Unmarshaller.strict[String, UuidId[A]] { str =>
+ UuidId[A](UUID.fromString(str))
+ }
+
+ implicit def numericIdUnmarshaller[A]: Unmarshaller[Long, NumericId[A]] =
+ Unmarshaller.strict[Long, NumericId[A]] { x =>
+ NumericId[A](x)
+ }
+
implicit def paramUnmarshaller[T](implicit reader: JsonReader[T]): Unmarshaller[String, T] =
Unmarshaller.firstOf(
Unmarshaller.strict((JsString(_: String)) andThen reader.read),
diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala
index 2aa3572..3e68d90 100644
--- a/src/test/scala/xyz/driver/core/JsonTest.scala
+++ b/src/test/scala/xyz/driver/core/JsonTest.scala
@@ -2,6 +2,7 @@ package xyz.driver.core
import java.net.InetAddress
import java.time.{Instant, LocalDate}
+import java.util.UUID
import akka.http.scaladsl.model.Uri
import akka.http.scaladsl.server.PathMatcher
@@ -41,6 +42,32 @@ class JsonTest extends WordSpec with Matchers with Inspectors {
}
}
+ "Json format for UuidId" should {
+ "read and write correct JSON" in {
+
+ val referenceId = UuidId[String](UUID.fromString("c21c0ba6-05a2-4d4b-87ba-2405a5e83e64"))
+
+ val writtenJson = json.uuidIdFormat.write(referenceId)
+ writtenJson.prettyPrint should be("\"c21c0ba6-05a2-4d4b-87ba-2405a5e83e64\"")
+
+ val parsedId = json.uuidIdFormat.read(writtenJson)
+ parsedId should be(referenceId)
+ }
+ }
+
+ "Json format for NumericId" should {
+ "read and write correct JSON" in {
+
+ val referenceId = NumericId[String](1312)
+
+ val writtenJson = json.numericIdFormat.write(referenceId)
+ writtenJson.prettyPrint should be("\"1312\"")
+
+ val parsedId = json.numericIdFormat.read(writtenJson)
+ parsedId should be(referenceId)
+ }
+ }
+
"Json format for @@" should {
"read and write correct JSON" in {
trait Irrelevant