aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksandr <ognelisar@gmail.com>2018-04-04 16:52:10 +0700
committerAleksandr <ognelisar@gmail.com>2018-04-04 16:52:10 +0700
commit223628e3c756701309ba8d33ecc886f43857fc15 (patch)
tree7cd251276898464c4745cc69f3c833e4ff567181
parent9ffa65c1ccdc5fdea4ccec26c4b39557d45867f7 (diff)
parentbdf9ec57f213eb652ba5fb3b21973d028034d40e (diff)
downloaddriver-core-223628e3c756701309ba8d33ecc886f43857fc15.tar.gz
driver-core-223628e3c756701309ba8d33ecc886f43857fc15.tar.bz2
driver-core-223628e3c756701309ba8d33ecc886f43857fc15.zip
Merge branch 'master' into TM-1431
-rw-r--r--build.sbt3
-rw-r--r--src/main/scala/xyz/driver/core/date.scala17
-rw-r--r--src/main/scala/xyz/driver/core/generators.scala3
-rw-r--r--src/main/scala/xyz/driver/core/json.scala53
-rw-r--r--src/test/scala/xyz/driver/core/GeneratorsTest.scala20
-rw-r--r--src/test/scala/xyz/driver/core/JsonTest.scala72
6 files changed, 148 insertions, 20 deletions
diff --git a/build.sbt b/build.sbt
index 88e4582..1249336 100644
--- a/build.sbt
+++ b/build.sbt
@@ -19,7 +19,8 @@ lazy val core = (project in file("."))
"com.typesafe.scala-logging" %% "scala-logging" % "3.5.0",
"eu.timepit" %% "refined" % "0.8.4",
"com.typesafe.slick" %% "slick" % "3.2.1",
- "org.mockito" % "mockito-core" % "1.9.5" % "test",
+ "com.beachape" %% "enumeratum" % "1.5.13",
+ "org.mockito" % "mockito-core" % "1.9.5" % Test,
"com.amazonaws" % "aws-java-sdk-s3" % "1.11.26",
"com.google.cloud" % "google-cloud-pubsub" % "0.25.0-beta",
"com.google.cloud" % "google-cloud-storage" % "1.7.0",
diff --git a/src/main/scala/xyz/driver/core/date.scala b/src/main/scala/xyz/driver/core/date.scala
index fe35c91..5454093 100644
--- a/src/main/scala/xyz/driver/core/date.scala
+++ b/src/main/scala/xyz/driver/core/date.scala
@@ -2,12 +2,13 @@ package xyz.driver.core
import java.util.Calendar
-import scala.util.Try
-
+import enumeratum._
import scalaz.std.anyVal._
-import scalaz.Scalaz.stringInstance
import scalaz.syntax.equal._
+import scala.collection.immutable.IndexedSeq
+import scala.util.Try
+
/**
* Driver Date type and related validators/extractors.
* Day, Month, and Year extractors are from ISO 8601 strings => driver...Date integers.
@@ -15,8 +16,8 @@ import scalaz.syntax.equal._
*/
object date {
- sealed trait DayOfWeek
- object DayOfWeek {
+ sealed trait DayOfWeek extends EnumEntry
+ object DayOfWeek extends Enum[DayOfWeek] {
case object Monday extends DayOfWeek
case object Tuesday extends DayOfWeek
case object Wednesday extends DayOfWeek
@@ -25,9 +26,11 @@ object date {
case object Saturday extends DayOfWeek
case object Sunday extends DayOfWeek
- val All: Set[DayOfWeek] = Set(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
+ val values: IndexedSeq[DayOfWeek] = findValues
+
+ val All: Set[DayOfWeek] = values.toSet
- def fromString(day: String): Option[DayOfWeek] = All.find(_.toString === day)
+ def fromString(day: String): Option[DayOfWeek] = withNameInsensitiveOption(day)
}
type Day = Int @@ Day.type
diff --git a/src/main/scala/xyz/driver/core/generators.scala b/src/main/scala/xyz/driver/core/generators.scala
index 143044c..3c85447 100644
--- a/src/main/scala/xyz/driver/core/generators.scala
+++ b/src/main/scala/xyz/driver/core/generators.scala
@@ -1,5 +1,6 @@
package xyz.driver.core
+import enumeratum._
import java.math.MathContext
import java.util.UUID
@@ -91,6 +92,8 @@ object generators {
def oneOf[T](items: Set[T]): T = items.toSeq(nextInt(items.size))
+ def oneOf[T <: EnumEntry](enum: Enum[T]): T = oneOf(enum.values: _*)
+
def arrayOf[T: ClassTag](generator: => T, maxLength: Int = DefaultMaxLength, minLength: Int = 0): Array[T] =
Array.fill(nextInt(maxLength, minLength))(generator)
diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala
index 4d7fa04..06a8837 100644
--- a/src/main/scala/xyz/driver/core/json.scala
+++ b/src/main/scala/xyz/driver/core/json.scala
@@ -3,21 +3,23 @@ package xyz.driver.core
import java.net.InetAddress
import java.util.{TimeZone, UUID}
-import scala.reflect.runtime.universe._
-import scala.util.Try
+import akka.http.scaladsl.marshalling.{Marshaller, Marshalling}
import akka.http.scaladsl.model.Uri.Path
-import akka.http.scaladsl.server._
import akka.http.scaladsl.server.PathMatcher.{Matched, Unmatched}
-import akka.http.scaladsl.marshalling.{Marshaller, Marshalling}
+import akka.http.scaladsl.server._
import akka.http.scaladsl.unmarshalling.Unmarshaller
+import enumeratum._
+import eu.timepit.refined.api.{Refined, Validate}
+import eu.timepit.refined.collection.NonEmpty
+import eu.timepit.refined.refineV
import spray.json._
import xyz.driver.core.auth.AuthCredentials
import xyz.driver.core.date.{Date, DayOfWeek, Month}
import xyz.driver.core.domain.{Email, PhoneNumber}
import xyz.driver.core.time.{Time, TimeOfDay}
-import eu.timepit.refined.refineV
-import eu.timepit.refined.api.{Refined, Validate}
-import eu.timepit.refined.collection.NonEmpty
+
+import scala.reflect.runtime.universe._
+import scala.util.Try
object json {
import DefaultJsonProtocol._
@@ -107,8 +109,7 @@ object json {
implicit val timeOfDayFormat: RootJsonFormat[TimeOfDay] = jsonFormat2(TimeOfDay.apply)
- implicit val dayOfWeekFormat: JsonFormat[DayOfWeek] =
- new EnumJsonFormat[DayOfWeek](DayOfWeek.All.map(w => w.toString -> w)(collection.breakOut): _*)
+ implicit val dayOfWeekFormat: JsonFormat[DayOfWeek] = new enumeratum.EnumJsonFormat(DayOfWeek)
implicit val dateFormat = new RootJsonFormat[Date] {
def write(date: Date) = JsString(date.toString)
@@ -136,9 +137,9 @@ object json {
}
implicit def revisionFromStringUnmarshaller[T]: Unmarshaller[String, Revision[T]] =
- Unmarshaller.strict[String, Revision[T]](Revision[T](_))
+ Unmarshaller.strict[String, Revision[T]](Revision[T])
- implicit def revisionFormat[T] = new RootJsonFormat[Revision[T]] {
+ implicit def revisionFormat[T]: RootJsonFormat[Revision[T]] = new RootJsonFormat[Revision[T]] {
def write(revision: Revision[T]) = JsString(revision.id.toString)
def read(value: JsValue): Revision[T] = value match {
@@ -186,6 +187,36 @@ object json {
JsString(obj.getHostAddress)
}
+ object enumeratum {
+
+ def enumUnmarshaller[T <: EnumEntry](enum: Enum[T]): Unmarshaller[String, T] =
+ Unmarshaller.strict { value =>
+ enum.withNameOption(value).getOrElse(unrecognizedValue(value, enum.values))
+ }
+
+ trait HasJsonFormat[T <: EnumEntry] { enum: Enum[T] =>
+
+ implicit val format: JsonFormat[T] = new EnumJsonFormat(enum)
+
+ implicit val unmarshaller: Unmarshaller[String, T] =
+ Unmarshaller.strict { value =>
+ enum.withNameOption(value).getOrElse(unrecognizedValue(value, enum.values))
+ }
+ }
+
+ class EnumJsonFormat[T <: EnumEntry](enum: Enum[T]) extends JsonFormat[T] {
+ override def read(json: JsValue): T = json match {
+ case JsString(name) => enum.withNameOption(name).getOrElse(unrecognizedValue(name, enum.values))
+ case _ => deserializationError("Expected string as enumeration value, but got " + json.toString)
+ }
+
+ override def write(obj: T): JsValue = JsString(obj.entryName)
+ }
+
+ private def unrecognizedValue(value: String, possibleValues: Seq[Any]): Nothing =
+ deserializationError(s"Unexpected value $value. Expected one of: ${possibleValues.mkString("[", ", ", "]")}")
+ }
+
class EnumJsonFormat[T](mapping: (String, T)*) extends RootJsonFormat[T] {
private val map = mapping.toMap
diff --git a/src/test/scala/xyz/driver/core/GeneratorsTest.scala b/src/test/scala/xyz/driver/core/GeneratorsTest.scala
index 62ba7ae..53a3aa9 100644
--- a/src/test/scala/xyz/driver/core/GeneratorsTest.scala
+++ b/src/test/scala/xyz/driver/core/GeneratorsTest.scala
@@ -2,6 +2,8 @@ package xyz.driver.core
import org.scalatest.{Assertions, FlatSpec, Matchers}
+import scala.collection.immutable.IndexedSeq
+
class GeneratorsTest extends FlatSpec with Matchers with Assertions {
import generators._
@@ -175,6 +177,24 @@ class GeneratorsTest extends FlatSpec with Matchers with Assertions {
Set(pick1, pick2, pick3, pick4, pick5, pick6).size should be >= 1
}
+ it should "be able to generate a specific value from an enumeratum enum" in {
+
+ import enumeratum._
+ sealed trait TestEnumValue extends EnumEntry
+ object TestEnum extends Enum[TestEnumValue] {
+ case object Value1 extends TestEnumValue
+ case object Value2 extends TestEnumValue
+ case object Value3 extends TestEnumValue
+ case object Value4 extends TestEnumValue
+ val values: IndexedSeq[TestEnumValue] = findValues
+ }
+
+ val picks = (1 to 100).map(_ => generators.oneOf(TestEnum))
+
+ TestEnum.values should contain allElementsOf picks
+ picks.toSet.size should be >= 1
+ }
+
it should "be able to generate array with values generated by generators" in {
val arrayOfTimes = arrayOf(nextTime(), 16)
diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala
index 827624c..7e8dba2 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 enumeratum._
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.numeric.Positive
import eu.timepit.refined.refineMV
@@ -11,8 +12,11 @@ import xyz.driver.core.time.provider.SystemTimeProvider
import spray.json._
import xyz.driver.core.TestTypes.CustomGADT
import xyz.driver.core.domain.{Email, PhoneNumber}
+import xyz.driver.core.json.enumeratum.HasJsonFormat
import xyz.driver.core.time.TimeOfDay
+import scala.collection.immutable.IndexedSeq
+
class JsonTest extends FlatSpec with Matchers {
import DefaultJsonProtocol._
@@ -116,7 +120,7 @@ class JsonTest extends FlatSpec with Matchers {
parsedPhoneNumber should be(referencePhoneNumber)
}
- "Json format for Enums" should "read and write correct JSON" in {
+ "Json format for ADT mappings" should "read and write correct JSON" in {
sealed trait EnumVal
case object Val1 extends EnumVal
@@ -141,6 +145,72 @@ class JsonTest extends FlatSpec with Matchers {
parsedEnumValue2 should be(referenceEnumValue2)
}
+ "Json format for Enums (external)" should "read and write correct JSON" in {
+
+ sealed trait MyEnum extends EnumEntry
+ object MyEnum extends Enum[MyEnum] {
+ case object Val1 extends MyEnum
+ case object `Val 2` extends MyEnum
+ case object `Val/3` extends MyEnum
+
+ val values: IndexedSeq[MyEnum] = findValues
+ }
+
+ val format = new enumeratum.EnumJsonFormat(MyEnum)
+
+ val referenceEnumValue1 = MyEnum.`Val 2`
+ val referenceEnumValue2 = MyEnum.`Val/3`
+
+ val writtenJson1 = format.write(referenceEnumValue1)
+ writtenJson1 shouldBe JsString("Val 2")
+
+ val writtenJson2 = format.write(referenceEnumValue2)
+ writtenJson2 shouldBe JsString("Val/3")
+
+ val parsedEnumValue1 = format.read(writtenJson1)
+ val parsedEnumValue2 = format.read(writtenJson2)
+
+ parsedEnumValue1 shouldBe referenceEnumValue1
+ parsedEnumValue2 shouldBe referenceEnumValue2
+
+ intercept[DeserializationException] {
+ format.read(JsString("Val4"))
+ }.getMessage shouldBe "Unexpected value Val4. Expected one of: [Val1, Val 2, Val/3]"
+ }
+
+ "Json format for Enums (automatic)" should "read and write correct JSON and not require import" in {
+
+ sealed trait MyEnum extends EnumEntry
+ object MyEnum extends Enum[MyEnum] with HasJsonFormat[MyEnum] {
+ case object Val1 extends MyEnum
+ case object `Val 2` extends MyEnum
+ case object `Val/3` extends MyEnum
+
+ val values: IndexedSeq[MyEnum] = findValues
+ }
+
+ val referenceEnumValue1: MyEnum = MyEnum.`Val 2`
+ val referenceEnumValue2: MyEnum = MyEnum.`Val/3`
+
+ val writtenJson1 = referenceEnumValue1.toJson
+ writtenJson1 shouldBe JsString("Val 2")
+
+ val writtenJson2 = referenceEnumValue2.toJson
+ writtenJson2 shouldBe JsString("Val/3")
+
+ import spray.json._
+
+ val parsedEnumValue1 = writtenJson1.prettyPrint.parseJson.convertTo[MyEnum]
+ val parsedEnumValue2 = writtenJson2.prettyPrint.parseJson.convertTo[MyEnum]
+
+ parsedEnumValue1 should be(referenceEnumValue1)
+ parsedEnumValue2 should be(referenceEnumValue2)
+
+ intercept[DeserializationException] {
+ JsString("Val4").convertTo[MyEnum]
+ }.getMessage shouldBe "Unexpected value Val4. Expected one of: [Val1, Val 2, Val/3]"
+ }
+
// Should be defined outside of case to have a TypeTag
case class CustomWrapperClass(value: Int)