aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--project/plugins.sbt2
-rw-r--r--scalastyle-config.xml2
-rw-r--r--src/main/scala/xyz/driver/core/core.scala5
-rw-r--r--src/main/scala/xyz/driver/core/database/Dal.scala47
-rw-r--r--src/main/scala/xyz/driver/core/database/database.scala (renamed from src/main/scala/xyz/driver/core/database.scala)63
-rw-r--r--src/main/scala/xyz/driver/core/database/package.scala31
-rw-r--r--src/main/scala/xyz/driver/core/date.scala25
-rw-r--r--src/main/scala/xyz/driver/core/generators.scala3
-rw-r--r--src/main/scala/xyz/driver/core/json.scala13
-rw-r--r--src/main/scala/xyz/driver/core/time.scala6
-rw-r--r--src/test/scala/xyz/driver/core/CoreTest.scala18
-rw-r--r--src/test/scala/xyz/driver/core/JsonTest.scala16
12 files changed, 173 insertions, 58 deletions
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 981c484..a4722e8 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,4 +1,4 @@
resolvers += "releases" at "https://drivergrp.jfrog.io/drivergrp/releases"
credentials += Credentials("Artifactory Realm", "drivergrp.jfrog.io", "sbt-publisher", "ANC-d8X-Whm-USS")
-addSbtPlugin("xyz.driver" % "sbt-settings" % "0.5.47")
+addSbtPlugin("xyz.driver" % "sbt-settings" % "0.5.48")
diff --git a/scalastyle-config.xml b/scalastyle-config.xml
index 811d745..1dd0fdf 100644
--- a/scalastyle-config.xml
+++ b/scalastyle-config.xml
@@ -64,7 +64,7 @@
</check>
<check level="error" class="org.scalastyle.scalariform.NumberOfTypesChecker" enabled="true">
<parameters>
- <parameter name="maxTypes"><![CDATA[30]]></parameter>
+ <parameter name="maxTypes"><![CDATA[50]]></parameter>
</parameters>
</check>
<check level="error" class="org.scalastyle.scalariform.CyclomaticComplexityChecker" enabled="true">
diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala
index 783150a..8c13aeb 100644
--- a/src/main/scala/xyz/driver/core/core.scala
+++ b/src/main/scala/xyz/driver/core/core.scala
@@ -20,6 +20,11 @@ package object core {
resource.close()
}
}
+
+ object tagging {
+ private[core] trait Tagged[+V, +Tag]
+ }
+ type @@[+V, +Tag] = V with tagging.Tagged[V, Tag]
}
package core {
diff --git a/src/main/scala/xyz/driver/core/database/Dal.scala b/src/main/scala/xyz/driver/core/database/Dal.scala
new file mode 100644
index 0000000..e920392
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/database/Dal.scala
@@ -0,0 +1,47 @@
+package xyz.driver.core.database
+
+import scala.concurrent.{ExecutionContext, Future}
+
+import scalaz.{ListT, Monad}
+import scalaz.std.scalaFuture._
+
+trait Dal {
+ protected type T[D]
+ protected implicit val monadT: Monad[T]
+
+ protected def execute[D](operations: T[D]): Future[D]
+ protected def noAction[V](v: V): T[V]
+ protected def customAction[R](action: => Future[R]): T[R]
+}
+
+class FutureDal(executionContext: ExecutionContext) extends Dal {
+ implicit val exec = executionContext
+ override type T[D] = Future[D]
+ implicit val monadT = implicitly[Monad[Future]]
+
+ def execute[D](operations: T[D]): Future[D] = operations
+ def noAction[V](v: V): T[V] = Future.successful(v)
+ def customAction[R](action: => Future[R]): T[R] = action
+}
+
+class SlickDal(database: Database, executionContext: ExecutionContext) extends Dal {
+ import database.profile.api._
+ implicit val exec = executionContext
+ override type T[D] = slick.dbio.DBIO[D]
+
+ implicit protected class QueryOps[+E, U](query: Query[E, U, Seq]) {
+ def resultT: ListT[T, U] = ListT[T, U](query.result.map(_.toList))
+ }
+
+ override implicit val monadT: Monad[T] = new Monad[T] {
+ override def point[A](a: => A): T[A] = DBIO.successful(a)
+ override def bind[A, B](fa: T[A])(f: A => T[B]): T[B] = fa.flatMap(f)
+ }
+
+ override def execute[D](readOperations: T[D]): Future[D] = {
+ database.database.run(readOperations.transactionally)
+ }
+
+ override def noAction[V](v: V): T[V] = DBIO.successful(v)
+ override def customAction[R](action: => Future[R]): T[R] = DBIO.from(action)
+}
diff --git a/src/main/scala/xyz/driver/core/database.scala b/src/main/scala/xyz/driver/core/database/database.scala
index a82e345..a8aec63 100644
--- a/src/main/scala/xyz/driver/core/database.scala
+++ b/src/main/scala/xyz/driver/core/database/database.scala
@@ -1,14 +1,13 @@
package xyz.driver.core
+import scala.concurrent.Future
+
import slick.backend.DatabaseConfig
-import slick.dbio.{DBIOAction, NoStream}
import slick.driver.JdbcProfile
import xyz.driver.core.time.Time
+import xyz.driver.core.date.Date
-import scala.concurrent.{ExecutionContext, Future}
-import scalaz.Monad
-
-object database {
+package database {
trait Database {
val profile: JdbcProfile
@@ -27,11 +26,6 @@ object database {
}
}
- type Schema = {
- def create: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema]
- def drop: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema]
- }
-
trait ColumnTypes {
val profile: JdbcProfile
import profile.api._
@@ -43,6 +37,9 @@ object database {
implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time] =
MappedColumnType.base[Time, Long](_.millis, Time(_))
+
+ implicit def `xyz.driver.core.time.Date.columnType`: BaseColumnType[Date] =
+ MappedColumnType.base[Date, java.sql.Date](dateToSqlDate(_), sqlDateToDate(_))
}
object ColumnTypes {
@@ -68,7 +65,6 @@ object database {
}
trait DatabaseObject extends ColumnTypes {
-
def createTables(): Future[Unit]
def disconnect(): Unit
}
@@ -77,49 +73,4 @@ object database {
def createTables(): Future[Unit] = Future.successful(())
def disconnect(): Unit = {}
}
-
- trait Dal {
-
- type T[_]
- implicit val monadT: Monad[T]
-
- def execute[D](operations: T[D]): Future[D]
- def noAction[V](v: V): T[V]
- def customAction[R](action: => Future[R]): T[R]
- }
-
- class FutureDal(executionContext: ExecutionContext) extends Dal {
-
- implicit val exec = executionContext
-
- override type T[_] = Future[_]
- implicit val monadT: Monad[T] = new Monad[T] {
- override def point[A](a: => A): T[A] = Future(a)
- override def bind[A, B](fa: T[A])(f: A => T[B]): T[B] = fa.flatMap(a => f(a.asInstanceOf[A]))
- }
-
- def execute[D](operations: T[D]): Future[D] = operations.asInstanceOf[Future[D]]
- def noAction[V](v: V): T[V] = Future.successful(v)
- def customAction[R](action: => Future[R]): T[R] = action
- }
-
- class SlickDal(database: Database, executionContext: ExecutionContext) extends Dal {
-
- import database.profile.api._
-
- implicit val exec = executionContext
-
- override type T[_] = slick.dbio.DBIO[_]
- val monadT: Monad[T] = new Monad[T] {
- override def point[A](a: => A): T[A] = DBIO.successful(a)
- override def bind[A, B](fa: T[A])(f: A => T[B]): T[B] = fa.flatMap(a => f(a.asInstanceOf[A]))
- }
-
- def execute[D](readOperations: T[D]): Future[D] = {
- database.database.run(readOperations.asInstanceOf[slick.dbio.DBIO[D]].transactionally)
- }
-
- def noAction[V](v: V): slick.dbio.DBIO[V] = DBIO.successful(v)
- def customAction[R](action: => Future[R]): T[R] = DBIO.from(action)
- }
}
diff --git a/src/main/scala/xyz/driver/core/database/package.scala b/src/main/scala/xyz/driver/core/database/package.scala
new file mode 100644
index 0000000..c88260b
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/database/package.scala
@@ -0,0 +1,31 @@
+package xyz.driver.core
+
+import java.sql.{Date => SqlDate}
+import java.util.Calendar
+
+import date.Date
+import slick.dbio.{DBIOAction, NoStream}
+
+package object database {
+
+ type Schema = {
+ def create: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema]
+ def drop: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema]
+ }
+
+ private[database] def sqlDateToDate(sqlDate: SqlDate): Date = {
+ // NOTE: SQL date does not have a time component, so this date
+ // should only be interpreted in the running JVMs timezone.
+ val cal = Calendar.getInstance()
+ cal.setTime(sqlDate)
+ Date(cal.get(Calendar.YEAR), date.tagMonth(cal.get(Calendar.MONTH)), cal.get(Calendar.DAY_OF_MONTH))
+ }
+
+ private[database] def dateToSqlDate(date: Date): SqlDate = {
+ val cal = Calendar.getInstance()
+ cal.set(Calendar.YEAR, date.year - 1900)
+ cal.set(Calendar.MONTH, date.month)
+ cal.set(Calendar.DAY_OF_MONTH, date.day)
+ new SqlDate(cal.getTime.getTime)
+ }
+}
diff --git a/src/main/scala/xyz/driver/core/date.scala b/src/main/scala/xyz/driver/core/date.scala
new file mode 100644
index 0000000..ab19074
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/date.scala
@@ -0,0 +1,25 @@
+package xyz.driver.core
+
+import java.util.Calendar
+
+object date {
+
+ type Month = Int @@ Month.type
+ private[core] def tagMonth(value: Int): Month = value.asInstanceOf[Month]
+
+ object Month {
+ val JANUARY = tagMonth(Calendar.JANUARY)
+ val FEBRUARY = tagMonth(Calendar.FEBRUARY)
+ val MARCH = tagMonth(Calendar.MARCH)
+ val APRIL = tagMonth(Calendar.APRIL)
+ val MAY = tagMonth(Calendar.MAY)
+ val JUNE = tagMonth(Calendar.JUNE)
+ val JULY = tagMonth(Calendar.JULY)
+ val AUGUST = tagMonth(Calendar.AUGUST)
+ val SEPTEMBER = tagMonth(Calendar.SEPTEMBER)
+ val OCTOBER = tagMonth(Calendar.OCTOBER)
+ val DECEMBER = tagMonth(Calendar.DECEMBER)
+ }
+
+ final case class Date(year: Int, month: Month, day: Int)
+}
diff --git a/src/main/scala/xyz/driver/core/generators.scala b/src/main/scala/xyz/driver/core/generators.scala
index c61cb94..d532ae3 100644
--- a/src/main/scala/xyz/driver/core/generators.scala
+++ b/src/main/scala/xyz/driver/core/generators.scala
@@ -4,6 +4,7 @@ import java.math.MathContext
import xyz.driver.core.revision.Revision
import xyz.driver.core.time.{Time, TimeRange}
+import xyz.driver.core.date.Date
import scala.reflect.ClassTag
import scala.util.Random
@@ -55,6 +56,8 @@ object generators {
Time(scala.math.max(oneTime.millis, anotherTime.millis)))
}
+ def nextDate(): Date = nextTime.toDate(java.util.TimeZone.getTimeZone("UTC"))
+
def nextBigDecimal(multiplier: Double = 1000000.00, precision: Int = 2): BigDecimal =
BigDecimal(multiplier * nextDouble, new MathContext(precision))
diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala
index 277543b..5833383 100644
--- a/src/main/scala/xyz/driver/core/json.scala
+++ b/src/main/scala/xyz/driver/core/json.scala
@@ -7,11 +7,14 @@ import akka.http.scaladsl.unmarshalling.Unmarshaller
import spray.json.{DeserializationException, JsNumber, _}
import xyz.driver.core.revision.Revision
import xyz.driver.core.time.Time
+import xyz.driver.core.date.{Date, Month}
import scala.reflect.runtime.universe._
object json {
+ import DefaultJsonProtocol._
+
def IdInPath[T]: PathMatcher1[Id[T]] = new PathMatcher1[Id[T]] {
def apply(path: Path) = path match {
case Path.Segment(segment, tail) => Matched(tail, Tuple1(Id[T](segment)))
@@ -66,6 +69,16 @@ object json {
}
}
+ implicit val monthFormat = new RootJsonFormat[Month] {
+ def write(month: Month) = JsNumber(month)
+ def read(value: JsValue): Month = value match {
+ case JsNumber(month) if 0 <= month && month <= 11 => date.tagMonth(month.toInt)
+ case _ => throw DeserializationException("Expected a number from 0 to 11")
+ }
+ }
+
+ implicit val dateFormat = jsonFormat3(Date)
+
def RevisionInPath[T]: PathMatcher1[Revision[T]] =
PathMatcher("""[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}""".r) flatMap { string =>
Some(Revision[T](string))
diff --git a/src/main/scala/xyz/driver/core/time.scala b/src/main/scala/xyz/driver/core/time.scala
index 6ff8209..cbb86ed 100644
--- a/src/main/scala/xyz/driver/core/time.scala
+++ b/src/main/scala/xyz/driver/core/time.scala
@@ -26,6 +26,12 @@ object time {
def isAfter(anotherTime: Time): Boolean = implicitly[Ordering[Time]].gt(this, anotherTime)
def advanceBy(duration: Duration): Time = Time(millis + duration.toMillis)
+
+ def toDate(timezone: TimeZone): date.Date = {
+ val cal = Calendar.getInstance(timezone)
+ cal.setTimeInMillis(millis)
+ date.Date(cal.get(Calendar.YEAR), date.tagMonth(cal.get(Calendar.MONTH)), cal.get(Calendar.DAY_OF_MONTH))
+ }
}
object Time {
diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala
index da9fd9a..e210887 100644
--- a/src/test/scala/xyz/driver/core/CoreTest.scala
+++ b/src/test/scala/xyz/driver/core/CoreTest.scala
@@ -60,6 +60,24 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar {
(y2 === y) should be(true)
}
+ "Time" should "use TimeZone correctly when converting to Date" in {
+
+ import time._
+
+ val EST = java.util.TimeZone.getTimeZone("EST")
+ val PST = java.util.TimeZone.getTimeZone("PST")
+
+ val timestamp = {
+ import java.util.Calendar
+ val cal = Calendar.getInstance(EST)
+ cal.set(Calendar.HOUR_OF_DAY, 1)
+ Time(cal.getTime().getTime())
+ }
+
+ textualDate(EST)(timestamp) should not be textualDate(PST)(timestamp)
+ timestamp.toDate(EST) should not be timestamp.toDate(PST)
+ }
+
"Name" should "have equality and ordering working correctly" in {
(Name[String]("foo") === Name[String]("foo")) should be(true)
diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala
index eb8d5d8..821b162 100644
--- a/src/test/scala/xyz/driver/core/JsonTest.scala
+++ b/src/test/scala/xyz/driver/core/JsonTest.scala
@@ -42,6 +42,22 @@ class JsonTest extends FlatSpec with Matchers {
parsedTime should be(referenceTime)
}
+ "Json format for Date" should "read and write correct JSON" in {
+ import date._
+
+ val referenceDate = Date(1941, Month.DECEMBER, 7)
+
+ val writtenJson = json.dateFormat.write(referenceDate)
+ writtenJson.prettyPrint should be("""|{
+ | "year": 1941,
+ | "month": 11,
+ | "day": 7
+ |}""".stripMargin)
+
+ val parsedDate = json.dateFormat.read(writtenJson)
+ parsedDate should be(referenceDate)
+ }
+
"Json format for Revision" should "read and write correct JSON" in {
val referenceRevision = Revision[String]("037e2ec0-8901-44ac-8e53-6d39f6479db4")