aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStewart Stewart <stewinsalot@gmail.com>2016-12-19 12:32:18 -0500
committerStewart Stewart <stewinsalot@gmail.com>2016-12-19 12:32:18 -0500
commit8515d672a9fdbb0eb9038a96cee661828cafa61a (patch)
tree3f04b773de6cea3def3566d2ca4bdff9b78ace8b
parent1702e1c44c45e36e2d6d289ef1b7d703f65ec422 (diff)
parent861ceb03e8faeb564dd027b13250b5604af8645f (diff)
downloaddriver-core-8515d672a9fdbb0eb9038a96cee661828cafa61a.tar.gz
driver-core-8515d672a9fdbb0eb9038a96cee661828cafa61a.tar.bz2
driver-core-8515d672a9fdbb0eb9038a96cee661828cafa61a.zip
Merge branch 'master' into implicit-companions
-rw-r--r--.gitignore1
-rw-r--r--.scalafmt24
-rw-r--r--build.sbt39
-rw-r--r--project/plugins.sbt2
-rw-r--r--scalastyle-config.xml1
-rw-r--r--src/main/resources/logback.xml22
-rw-r--r--src/main/scala/com/drivergrp/core/auth.scala137
-rw-r--r--src/main/scala/com/drivergrp/core/core.scala46
-rw-r--r--src/main/scala/com/drivergrp/core/crypto.scala19
-rw-r--r--src/main/scala/com/drivergrp/core/database.scala52
-rw-r--r--src/main/scala/com/drivergrp/core/json.scala106
-rw-r--r--src/main/scala/com/drivergrp/core/stats.scala43
-rw-r--r--src/main/scala/xyz/driver/core/app.scala (renamed from src/main/scala/com/drivergrp/core/app.scala)135
-rw-r--r--src/main/scala/xyz/driver/core/auth.scala120
-rw-r--r--src/main/scala/xyz/driver/core/config.scala (renamed from src/main/scala/com/drivergrp/core/config.scala)2
-rw-r--r--src/main/scala/xyz/driver/core/core.scala52
-rw-r--r--src/main/scala/xyz/driver/core/database.scala125
-rw-r--r--src/main/scala/xyz/driver/core/file.scala (renamed from src/main/scala/com/drivergrp/core/file.scala)31
-rw-r--r--src/main/scala/xyz/driver/core/generators.scala (renamed from src/main/scala/com/drivergrp/core/generators.scala)24
-rw-r--r--src/main/scala/xyz/driver/core/json.scala158
-rw-r--r--src/main/scala/xyz/driver/core/logging.scala (renamed from src/main/scala/com/drivergrp/core/logging.scala)121
-rw-r--r--src/main/scala/xyz/driver/core/messages.scala (renamed from src/main/scala/com/drivergrp/core/messages.scala)4
-rw-r--r--src/main/scala/xyz/driver/core/rest.scala (renamed from src/main/scala/com/drivergrp/core/rest.scala)77
-rw-r--r--src/main/scala/xyz/driver/core/stats.scala97
-rw-r--r--src/main/scala/xyz/driver/core/time.scala (renamed from src/main/scala/com/drivergrp/core/time.scala)2
-rw-r--r--src/test/scala/com/drivergrp/core/AuthTest.scala77
-rw-r--r--src/test/scala/xyz/driver/core/AuthTest.scala79
-rw-r--r--src/test/scala/xyz/driver/core/CoreTest.scala (renamed from src/test/scala/com/drivergrp/core/CoreTest.scala)16
-rw-r--r--src/test/scala/xyz/driver/core/FileTest.scala (renamed from src/test/scala/com/drivergrp/core/FileTest.scala)16
-rw-r--r--src/test/scala/xyz/driver/core/GeneratorsTest.scala (renamed from src/test/scala/com/drivergrp/core/GeneratorsTest.scala)38
-rw-r--r--src/test/scala/xyz/driver/core/JsonTest.scala (renamed from src/test/scala/com/drivergrp/core/JsonTest.scala)64
-rw-r--r--src/test/scala/xyz/driver/core/MessagesTest.scala (renamed from src/test/scala/com/drivergrp/core/MessagesTest.scala)20
-rw-r--r--src/test/scala/xyz/driver/core/StatsTest.scala (renamed from src/test/scala/com/drivergrp/core/StatsTest.scala)10
-rw-r--r--src/test/scala/xyz/driver/core/TestTypes.scala14
-rw-r--r--src/test/scala/xyz/driver/core/TimeTest.scala (renamed from src/test/scala/com/drivergrp/core/TimeTest.scala)12
35 files changed, 1037 insertions, 749 deletions
diff --git a/.gitignore b/.gitignore
index 5ffc222..9ecad8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,5 @@ project/plugins/project/
.idea/
.idea
.scalafmt
+.scalafmt.conf
scalastyle-config.xml
diff --git a/.scalafmt b/.scalafmt
deleted file mode 100644
index 5226b38..0000000
--- a/.scalafmt
+++ /dev/null
@@ -1,24 +0,0 @@
-# scalafmt sbt plugin config
-# refer to https://olafurpg.github.io/scalafmt/#Configuration for properties
-
---style defaultWithAlign # For pretty alignment.
---maxColumn 120 # For my wide 30" display.
-
---reformatDocstrings true
---scalaDocs
-
---continuationIndentCallSite 4
---continuationIndentDefnSite 4
-
---rewriteTokens ⇒;=>,←;<-
---danglingParentheses false
---spaceAfterTripleEquals true
---alignByArrowEnumeratorGenerator true
---binPackParentConstructors true
---allowNewlineBeforeColonInMassiveReturnTypes true
---spacesInImportCurlyBraces false
-
-# --alignByOpenParenCallSite <value>
-# --alignByOpenParenDefnSite <value>
-
- \ No newline at end of file
diff --git a/build.sbt b/build.sbt
index 854ae44..09bd2fb 100644
--- a/build.sbt
+++ b/build.sbt
@@ -3,26 +3,25 @@ import Keys._
lazy val akkaHttpV = "2.4.8"
-lazy val core = (project in file(".")).
- settings(name := "core").
- settings(
+lazy val core = (project in file("."))
+ .settings(name := "core")
+ .settings(
libraryDependencies ++= Seq(
- "com.typesafe.akka" %% "akka-http-core" % akkaHttpV,
- "com.typesafe.akka" %% "akka-http-experimental" % akkaHttpV,
- "com.typesafe.akka" %% "akka-http-jackson-experimental" % akkaHttpV,
- "com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaHttpV,
- "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpV,
- "org.scalatest" % "scalatest_2.11" % "2.2.6" % "test",
- "org.scalacheck" %% "scalacheck" % "1.12.5" % "test",
- "org.mockito" % "mockito-core" % "1.9.5" % "test",
- "com.amazonaws" % "aws-java-sdk-s3" % "1.11.26",
- "com.typesafe.slick" %% "slick" % "3.1.1",
- "com.typesafe" % "config" % "1.2.1",
- "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0",
- "ch.qos.logback" % "logback-classic" % "1.1.3",
- "org.slf4j" % "slf4j-nop" % "1.6.4",
- "com.github.swagger-akka-http" %% "swagger-akka-http" % "0.7.1"
+ "com.typesafe.akka" %% "akka-http-core" % akkaHttpV,
+ "com.typesafe.akka" %% "akka-http-experimental" % akkaHttpV,
+ "com.typesafe.akka" %% "akka-http-jackson-experimental" % akkaHttpV,
+ "com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaHttpV,
+ "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpV,
+ "org.scalatest" % "scalatest_2.11" % "2.2.6" % "test",
+ "org.scalacheck" %% "scalacheck" % "1.12.5" % "test",
+ "org.mockito" % "mockito-core" % "1.9.5" % "test",
+ "com.amazonaws" % "aws-java-sdk-s3" % "1.11.26",
+ "com.typesafe.slick" %% "slick" % "3.1.1",
+ "com.typesafe" % "config" % "1.2.1",
+ "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0",
+ "ch.qos.logback" % "logback-classic" % "1.1.3",
+ "com.github.swagger-akka-http" %% "swagger-akka-http" % "0.7.1"
))
.gitPluginConfiguration
- .settings (lintingSettings ++ formatSettings)
- .settings (repositoriesSettings ++ publicationSettings ++ releaseSettings)
+ .settings(lintingSettings ++ formatSettings)
+ .settings(repositoriesSettings ++ publicationSettings ++ releaseSettings)
diff --git a/project/plugins.sbt b/project/plugins.sbt
index dde5a53..981c484 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.16")
+addSbtPlugin("xyz.driver" % "sbt-settings" % "0.5.47")
diff --git a/scalastyle-config.xml b/scalastyle-config.xml
index bba1e69..811d745 100644
--- a/scalastyle-config.xml
+++ b/scalastyle-config.xml
@@ -88,6 +88,7 @@
<check level="error" class="org.scalastyle.scalariform.MethodNamesChecker" enabled="true">
<parameters>
<parameter name="regex"><![CDATA[^[A-Za-z\\*][A-Za-z0-9]*$]]></parameter>
+ <parameter name="ignoreRegex">`.*`</parameter>
</parameters>
</check>
<check level="error" class="org.scalastyle.scalariform.ClassTypeParameterChecker" enabled="false">
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
deleted file mode 100644
index 1b96003..0000000
--- a/src/main/resources/logback.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<configuration>
-
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <target>System.out</target>
- <encoder>
- <pattern>%date{MM/dd HH:mm:ss} %-5level[%.15thread] %logger{1} - %msg%n</pattern>
- </encoder>
- </appender>
- <logger name="slick.backend.DatabaseComponent.actio" level="debug"/>
- <logger name="slick.jdbc" level="error" />
- <logger name="slick.ast" level="error" />
- <logger name="slick.memory" level="error" />
- <logger name="slick.relational" level="error" />
- <logger name="slick.compiler" level="error" />
- <logger name="com.wordnik" level="error" />
- <logger name="com.github" level="error" />
- <root level="debug">
- <appender-ref ref="CONSOLE"/>
- </root>
-
-</configuration>
diff --git a/src/main/scala/com/drivergrp/core/auth.scala b/src/main/scala/com/drivergrp/core/auth.scala
deleted file mode 100644
index 6b8cdaa..0000000
--- a/src/main/scala/com/drivergrp/core/auth.scala
+++ /dev/null
@@ -1,137 +0,0 @@
-package com.drivergrp.core
-
-import akka.http.scaladsl.model.headers.HttpChallenges
-import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected
-
-import scala.concurrent.Future
-import scala.util.{Failure, Success, Try}
-import scalaz.OptionT
-
-object auth {
-
- sealed trait Permission
- case object CanSeeUser extends Permission
- case object CanSeeAssay extends Permission
- case object CanSeeReport extends Permission
- case object CanCreateReport extends Permission
- case object CanEditReport extends Permission
- case object CanEditReviewingReport extends Permission
- case object CanSignOutReport extends Permission
- case object CanShareReportWithPatient extends Permission
- case object CanAssignRoles extends Permission
-
- trait Role {
- val id: Id[Role]
- val name: Name[Role]
- val permissions: Set[Permission]
-
- def hasPermission(permission: Permission): Boolean = permissions.contains(permission)
- }
-
- case object ObserverRole extends Role {
- val id = Id(1L)
- val name = Name("observer")
- val permissions = Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport)
- }
-
- case object PatientRole extends Role {
- val id = Id(2L)
- val name = Name("patient")
- val permissions = Set.empty[Permission]
- }
-
- case object CuratorRole extends Role {
- val id = Id(3L)
- val name = Name("curator")
- val permissions = Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport, CanEditReport)
- }
-
- case object PathologistRole extends Role {
- val id = Id(4L)
- val name = Name("pathologist")
- val permissions =
- Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport, CanEditReport, CanSignOutReport, CanEditReviewingReport)
- }
-
- case object AdministratorRole extends Role {
- val id = Id(5L)
- val name = Name("administrator")
- val permissions = Set[Permission](
- CanSeeUser,
- CanSeeAssay,
- CanSeeReport,
- CanCreateReport,
- CanEditReport,
- CanEditReviewingReport,
- CanSignOutReport,
- CanShareReportWithPatient,
- CanAssignRoles
- )
- }
-
- trait User {
- def id: Id[User]
- def roles: Set[Role]
- def permissions: Set[Permission] = roles.flatMap(_.permissions)
- }
-
- final case class Macaroon(value: String)
-
- final case class Base64[T](value: String)
-
- final case class AuthToken(value: Base64[Macaroon])
-
- final case class PasswordHash(value: String)
-
- object AuthService {
- val AuthenticationTokenHeader = "WWW-Authenticate"
- }
-
- trait AuthService[U <: User] {
-
- import akka.http.scaladsl.server._
- import Directives._
-
- protected def authStatus(authToken: AuthToken): OptionT[Future, U]
-
- def authorize(permission: Permission): Directive1[(AuthToken, U)] = {
- parameters('authToken.?).flatMap { parameterTokenValue =>
- optionalHeaderValueByName(AuthService.AuthenticationTokenHeader).flatMap { headerTokenValue =>
- verifyAuthToken(headerTokenValue.orElse(parameterTokenValue), permission)
- }
- }
- }
-
- private def verifyAuthToken(tokenOption: Option[String], permission: Permission): Directive1[(AuthToken, U)] =
- tokenOption match {
- case Some(tokenValue) =>
- val token = AuthToken(Base64[Macaroon](tokenValue))
-
- onComplete(authStatus(token).run).flatMap { tokenUserResult =>
- checkPermissions(tokenUserResult, permission, token)
- }
-
- case None =>
- reject(MissingHeaderRejection(AuthService.AuthenticationTokenHeader))
- }
-
- private def checkPermissions(userResult: Try[Option[U]],
- permission: Permission,
- token: AuthToken): Directive1[(AuthToken, U)] = {
- userResult match {
- case Success(Some(user)) =>
- if (user.roles.exists(_.hasPermission(permission))) provide(token -> user)
- else {
- val challenge = HttpChallenges.basic(s"User does not have the required permission $permission")
- reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
- }
-
- case Success(None) =>
- reject(ValidationRejection(s"Wasn't able to find authenticated user for the token provided"))
-
- case Failure(t) =>
- reject(ValidationRejection(s"Wasn't able to verify token for authenticated user", Some(t)))
- }
- }
- }
-}
diff --git a/src/main/scala/com/drivergrp/core/core.scala b/src/main/scala/com/drivergrp/core/core.scala
deleted file mode 100644
index 158447f..0000000
--- a/src/main/scala/com/drivergrp/core/core.scala
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.drivergrp
-
-import scalaz.Equal
-
-package object core {
- import scala.language.reflectiveCalls
-
- def make[T](v: => T)(f: T => Unit): T = {
- val value = v; f(value); value
- }
-
- def using[R <: { def close() }, P](r: => R)(f: R => P): P = {
- val resource = r
- try {
- f(resource)
- } finally {
- resource.close()
- }
- }
-
- object tagging {
- private[core] trait Tagged[+V, +Tag]
- }
- type @@[+V, +Tag] = V with tagging.Tagged[V, Tag]
-
- type Id[+Tag] = Long @@ Tag
- object Id {
- def apply[Tag](value: Long) = value.asInstanceOf[Id[Tag]]
- }
- implicit def idEqual[T]: Equal[Id[T]] = Equal.equal[Id[T]](_ == _)
- implicit def idOrdering[T]: Ordering[Id[T]] = Ordering.by(i => i: Long)
-
- type Name[+Tag] = String @@ Tag
- object Name {
- def apply[Tag](value: String) = value.asInstanceOf[Name[Tag]]
- }
-
- implicit def nameEqual[T]: Equal[Name[T]] = Equal.equal[Name[T]](_ == _)
- implicit def nameOrdering[T]: Ordering[Name[T]] = Ordering.by(n => n: String)
-
- object revision {
- final case class Revision[T](id: String)
-
- implicit def revisionEqual[T]: Equal[Revision[T]] = Equal.equal[Revision[T]](_.id == _.id)
- }
-}
diff --git a/src/main/scala/com/drivergrp/core/crypto.scala b/src/main/scala/com/drivergrp/core/crypto.scala
deleted file mode 100644
index f693fa3..0000000
--- a/src/main/scala/com/drivergrp/core/crypto.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.drivergrp.core
-
-import com.drivergrp.core.auth.AuthToken
-
-object crypto {
-
- final case class EncryptionKey(value: String)
-
- final case class DecryptionKey(value: String)
-
- trait Crypto {
-
- def keyForToken(authToken: AuthToken): EncryptionKey
-
- def encrypt(encryptionKey: EncryptionKey)(message: Array[Byte]): Array[Byte]
-
- def decrypt(decryptionKey: EncryptionKey)(message: Array[Byte]): Array[Byte]
- }
-}
diff --git a/src/main/scala/com/drivergrp/core/database.scala b/src/main/scala/com/drivergrp/core/database.scala
deleted file mode 100644
index 581c5de..0000000
--- a/src/main/scala/com/drivergrp/core/database.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.drivergrp.core
-
-import com.drivergrp.core.time.Time
-
-import scala.concurrent.Future
-import slick.backend.DatabaseConfig
-import slick.driver.JdbcProfile
-
-object database {
-
- trait Database {
- val profile: JdbcProfile
- val database: JdbcProfile#Backend#Database
- }
-
- object Database {
-
- def fromConfig(databaseName: String): Database = {
- val dbConfig: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig(databaseName)
-
- new Database {
- val profile: JdbcProfile = dbConfig.driver
- val database: JdbcProfile#Backend#Database = dbConfig.db
- }
- }
- }
-
- trait IdColumnTypes {
- val database: Database
-
- import database.profile.api._
-
- implicit def idColumnType[T] =
- MappedColumnType.base[Id[T], Long](id => id: Long, Id[T](_))
-
- implicit def nameColumnType[T] =
- MappedColumnType.base[Name[T], String](name => name: String, Name[T](_))
-
- implicit val timeColumnType = MappedColumnType.base[Time, Long](time => time.millis, Time(_))
- }
-
- trait DatabaseObject extends IdColumnTypes {
-
- def createTables(): Future[Unit]
- def disconnect(): Unit
- }
-
- abstract class DatabaseObjectAdapter extends DatabaseObject {
- def createTables(): Future[Unit] = Future.successful(())
- def disconnect(): Unit = {}
- }
-}
diff --git a/src/main/scala/com/drivergrp/core/json.scala b/src/main/scala/com/drivergrp/core/json.scala
deleted file mode 100644
index 9a30161..0000000
--- a/src/main/scala/com/drivergrp/core/json.scala
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.drivergrp.core
-
-import akka.http.scaladsl.model.Uri.Path
-import akka.http.scaladsl.server.PathMatcher.Matched
-import akka.http.scaladsl.server.{PathMatcher, _}
-import akka.http.scaladsl.unmarshalling.Unmarshaller
-import com.drivergrp.core.revision.Revision
-import com.drivergrp.core.time.Time
-import spray.json.{DeserializationException, JsNumber, _}
-
-import scala.reflect.runtime.universe._
-
-object json {
-
- def IdInPath[T]: PathMatcher1[Id[T]] =
- PathMatcher("""[+-]?\d*""".r) flatMap { string =>
- try Some(Id[T](string.toLong))
- catch { case _: IllegalArgumentException => None }
- }
-
- implicit def idFormat[T] = new RootJsonFormat[Id[T]] {
- def write(id: Id[T]) = JsNumber(id)
-
- def read(value: JsValue) = value match {
- case JsNumber(id) => Id[T](id.toLong)
- case _ => throw new DeserializationException("Id expects number")
- }
- }
-
- def NameInPath[T]: PathMatcher1[Name[T]] = new PathMatcher1[Name[T]] {
- def apply(path: Path) = Matched(Path.Empty, Tuple1(Name[T](path.toString)))
- }
-
- implicit def nameFormat[T] = new RootJsonFormat[Name[T]] {
- def write(name: Name[T]) = JsString(name)
-
- def read(value: JsValue): Name[T] = value match {
- case JsString(name) => Name[T](name)
- case _ => throw new DeserializationException("Name expects string")
- }
- }
-
- def TimeInPath: PathMatcher1[Time] =
- PathMatcher("""[+-]?\d*""".r) flatMap { string =>
- try Some(Time(string.toLong))
- catch { case _: IllegalArgumentException => None }
- }
-
- implicit val timeFormat = new RootJsonFormat[Time] {
- def write(time: Time) = JsObject("timestamp" -> JsNumber(time.millis))
-
- def read(value: JsValue): Time = value match {
- case JsObject(fields) =>
- fields
- .get("timestamp")
- .flatMap {
- case JsNumber(millis) => Some(Time(millis.toLong))
- case _ => None
- }
- .getOrElse(throw new DeserializationException("Time expects number"))
- case _ => throw new DeserializationException("Time expects number")
- }
- }
-
- 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))
- }
-
- implicit def revisionFromStringUnmarshaller[T]: Unmarshaller[String, Revision[T]] =
- Unmarshaller.strict[String, Revision[T]](Revision[T](_))
-
- implicit def revisionFormat[T] = new RootJsonFormat[Revision[T]] {
- def write(revision: Revision[T]) = JsString(revision.id.toString)
-
- def read(value: JsValue): Revision[T] = value match {
- case JsString(revision) => Revision[T](revision)
- case _ => throw new DeserializationException("Revision expects uuid string")
- }
- }
-
- class EnumJsonFormat[T](mapping: (String, T)*) extends JsonFormat[T] {
- private val map = mapping.toMap
-
- override def write(value: T): JsValue = {
- map.find(_._2 == value).map(_._1) match {
- case Some(name) => JsString(name)
- case _ => serializationError(s"Value $value is not found in the mapping $map")
- }
- }
-
- override def read(json: JsValue): T = json match {
- case JsString(name) =>
- map.getOrElse(name, throw new DeserializationException(s"Value $name is not found in the mapping $map"))
- case _ => deserializationError("Expected string as enumeration value, but got " + json)
- }
- }
-
- class ValueClassFormat[T: TypeTag](writeValue: T => BigDecimal, create: BigDecimal => T) extends JsonFormat[T] {
- def write(valueClass: T) = JsNumber(writeValue(valueClass))
- def read(json: JsValue): T = json match {
- case JsNumber(value) => create(value)
- case _ => deserializationError(s"Expected number as ${typeOf[T].getClass.getName}, but got " + json)
- }
- }
-}
diff --git a/src/main/scala/com/drivergrp/core/stats.scala b/src/main/scala/com/drivergrp/core/stats.scala
deleted file mode 100644
index cd77f7a..0000000
--- a/src/main/scala/com/drivergrp/core/stats.scala
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.drivergrp.core
-
-import com.drivergrp.core.logging.Logger
-import com.drivergrp.core.time.{Time, TimeRange}
-
-object stats {
-
- type StatsKey = String
- type StatsKeys = Seq[StatsKey]
-
- trait Stats {
-
- def recordStats(keys: StatsKeys, interval: TimeRange, value: BigDecimal): Unit
-
- def recordStats(keys: StatsKeys, interval: TimeRange, value: Int): Unit =
- recordStats(keys, interval, BigDecimal(value))
-
- def recordStats(key: StatsKey, interval: TimeRange, value: BigDecimal): Unit =
- recordStats(Vector(key), interval, value)
-
- def recordStats(key: StatsKey, interval: TimeRange, value: Int): Unit =
- recordStats(Vector(key), interval, BigDecimal(value))
-
- def recordStats(keys: StatsKeys, time: Time, value: BigDecimal): Unit =
- recordStats(keys, TimeRange(time, time), value)
-
- def recordStats(keys: StatsKeys, time: Time, value: Int): Unit =
- recordStats(keys, TimeRange(time, time), BigDecimal(value))
-
- def recordStats(key: StatsKey, time: Time, value: BigDecimal): Unit =
- recordStats(Vector(key), TimeRange(time, time), value)
-
- def recordStats(key: StatsKey, time: Time, value: Int): Unit =
- recordStats(Vector(key), TimeRange(time, time), BigDecimal(value))
- }
-
- class LogStats(log: Logger) extends Stats {
- def recordStats(keys: StatsKeys, interval: TimeRange, value: BigDecimal): Unit = {
- val valueString = value.bigDecimal.toPlainString
- log.audit(s"${keys.mkString(".")}(${interval.start.millis}-${interval.end.millis})=$valueString")
- }
- }
-}
diff --git a/src/main/scala/com/drivergrp/core/app.scala b/src/main/scala/xyz/driver/core/app.scala
index a1e1082..227be57 100644
--- a/src/main/scala/com/drivergrp/core/app.scala
+++ b/src/main/scala/xyz/driver/core/app.scala
@@ -1,21 +1,24 @@
-package com.drivergrp.core
+package xyz.driver.core
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.StatusCodes._
+import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.RouteResult._
import akka.http.scaladsl.server.{ExceptionHandler, Route, RouteConcatenation}
import akka.stream.ActorMaterializer
-import com.drivergrp.core.logging.{Logger, TypesafeScalaLogger}
-import com.drivergrp.core.rest.Swagger
-import com.drivergrp.core.time.Time
-import com.drivergrp.core.time.provider.{SystemTimeProvider, TimeProvider}
import com.typesafe.config.Config
import org.slf4j.LoggerFactory
import spray.json.DefaultJsonProtocol
+import xyz.driver.core
+import xyz.driver.core.logging.{Logger, TypesafeScalaLogger}
+import xyz.driver.core.rest.{ContextHeaders, Swagger}
+import xyz.driver.core.stats.SystemStats
+import xyz.driver.core.time.Time
+import xyz.driver.core.time.provider.{SystemTimeProvider, TimeProvider}
import scala.compat.Platform.ConcurrentModificationException
import scala.concurrent.duration._
@@ -28,8 +31,8 @@ object app {
modules: Seq[Module],
time: TimeProvider = new SystemTimeProvider(),
log: Logger = new TypesafeScalaLogger(
- com.typesafe.scalalogging.Logger(LoggerFactory.getLogger(classOf[DriverApp]))),
- config: Config = com.drivergrp.core.config.loadDefaultConfig,
+ com.typesafe.scalalogging.Logger(LoggerFactory.getLogger(classOf[DriverApp]))),
+ config: Config = core.config.loadDefaultConfig,
interface: String = "::0",
baseUrl: String = "localhost:8080",
port: Int = 8080) {
@@ -61,49 +64,43 @@ object app {
val swaggerRoutes = swaggerService.routes ~ swaggerService.swaggerUI
val versionRt = versionRoute(version, gitHash, time.currentTime())
- val generalExceptionHandler = ExceptionHandler {
-
- case is: IllegalStateException =>
- extractUri { uri =>
- // TODO: extract `requestUuid` from request or thread, provided by linkerd/zipkin
- def requestUuid = java.util.UUID.randomUUID.toString
-
- log.debug(s"Request is not allowed to $uri ($requestUuid)", is)
- complete(
- HttpResponse(BadRequest,
- entity = s"""{ "requestUuid": "$requestUuid", "message": "${is.getMessage}" }"""))
- }
-
- case cm: ConcurrentModificationException =>
- extractUri { uri =>
- // TODO: extract `requestUuid` from request or thread, provided by linkerd/zipkin
- def requestUuid = java.util.UUID.randomUUID.toString
-
- log.debug(s"Concurrent modification of the resource $uri ($requestUuid)", cm)
- complete(
- HttpResponse(Conflict,
- entity = s"""{ "requestUuid": "$requestUuid", "message": "${cm.getMessage}" }"""))
- }
+ val _ = Future {
+ http.bindAndHandle(route2HandlerFlow(handleExceptions(ExceptionHandler(exceptionHandler)) { ctx =>
+ val trackingId = rest.extractTrackingId(ctx)
+ log.audit(s"Received request ${ctx.request} with tracking id $trackingId")
- case t: Throwable =>
- extractUri { uri =>
- // TODO: extract `requestUuid` from request or thread, provided by linkerd/zipkin
- def requestUuid = java.util.UUID.randomUUID.toString
+ val contextWithTrackingId =
+ ctx.withRequest(ctx.request.addHeader(RawHeader(ContextHeaders.TrackingIdHeader, trackingId)))
- log.error(s"Request to $uri could not be handled normally ($requestUuid)", t)
- complete(
- HttpResponse(InternalServerError,
- entity = s"""{ "requestUuid": "$requestUuid", "message": "${t.getMessage}" }"""))
- }
- }
-
- val _ = Future {
- http.bindAndHandle(route2HandlerFlow(handleExceptions(generalExceptionHandler) {
- logRequestResult("log")(modules.map(_.route).foldLeft(versionRt ~ swaggerRoutes)(_ ~ _))
+ respondWithHeaders(List(RawHeader(ContextHeaders.TrackingIdHeader, trackingId))) {
+ modules.map(_.route).foldLeft(versionRt ~ healthRoute ~ swaggerRoutes)(_ ~ _)
+ }(contextWithTrackingId)
}), interface, port)(materializer)
}
}
+ protected def exceptionHandler = PartialFunction[Throwable, Route] {
+
+ case is: IllegalStateException =>
+ ctx =>
+ val trackingId = rest.extractTrackingId(ctx)
+ log.debug(s"Request is not allowed to ${ctx.request.uri} ($trackingId)", is)
+ complete(HttpResponse(BadRequest, entity = is.getMessage))(ctx)
+
+ case cm: ConcurrentModificationException =>
+ ctx =>
+ val trackingId = rest.extractTrackingId(ctx)
+ log.audit(s"Concurrent modification of the resource ${ctx.request.uri} ($trackingId)", cm)
+ complete(
+ HttpResponse(Conflict, entity = "Resource was changed concurrently, try requesting a newer version"))(ctx)
+
+ case t: Throwable =>
+ ctx =>
+ val trackingId = rest.extractTrackingId(ctx)
+ log.error(s"Request to ${ctx.request.uri} could not be handled normally ($trackingId)", t)
+ complete(HttpResponse(InternalServerError, entity = t.getMessage))(ctx)
+ }
+
protected def versionRoute(version: String, gitHash: String, startupTime: Time): Route = {
import DefaultJsonProtocol._
import SprayJsonSupport._
@@ -111,14 +108,46 @@ object app {
path("version") {
val currentTime = time.currentTime().millis
complete(
- Map(
- "version" -> version,
- "gitHash" -> gitHash,
- "modules" -> modules.map(_.name).mkString(", "),
- "startupTime" -> startupTime.millis.toString,
- "serverTime" -> currentTime.toString,
- "uptime" -> (currentTime - startupTime.millis).toString
- ))
+ Map(
+ "version" -> version,
+ "gitHash" -> gitHash,
+ "modules" -> modules.map(_.name).mkString(", "),
+ "startupTime" -> startupTime.millis.toString,
+ "serverTime" -> currentTime.toString,
+ "uptime" -> (currentTime - startupTime.millis).toString
+ ))
+ }
+ }
+
+ protected def healthRoute: Route = {
+ import DefaultJsonProtocol._
+ import SprayJsonSupport._
+ import spray.json._
+
+ val memoryUsage = SystemStats.memoryUsage
+ val gcStats = SystemStats.garbageCollectorStats
+
+ path("health") {
+ complete(
+ Map(
+ "availableProcessors" -> SystemStats.availableProcessors.toJson,
+ "memoryUsage" -> Map(
+ "free" -> memoryUsage.free.toJson,
+ "total" -> memoryUsage.total.toJson,
+ "max" -> memoryUsage.max.toJson
+ ).toJson,
+ "gcStats" -> Map(
+ "garbageCollectionTime" -> gcStats.garbageCollectionTime.toJson,
+ "totalGarbageCollections" -> gcStats.totalGarbageCollections.toJson
+ ).toJson,
+ "fileSystemSpace" -> SystemStats.fileSystemSpace.map { f =>
+ Map("path" -> f.path.toJson,
+ "freeSpace" -> f.freeSpace.toJson,
+ "totalSpace" -> f.totalSpace.toJson,
+ "usableSpace" -> f.usableSpace.toJson)
+ }.toJson,
+ "operatingSystem" -> SystemStats.operatingSystemStats.toJson
+ ))
}
}
@@ -173,7 +202,7 @@ object app {
}
class EmptyModule extends Module {
- val name = "Nothing"
+ val name = "Nothing"
def route: Route = complete(StatusCodes.OK)
def routeTypes = Seq.empty[Type]
}
diff --git a/src/main/scala/xyz/driver/core/auth.scala b/src/main/scala/xyz/driver/core/auth.scala
new file mode 100644
index 0000000..0b30bc0
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/auth.scala
@@ -0,0 +1,120 @@
+package xyz.driver.core
+
+import akka.http.scaladsl.model.headers.HttpChallenges
+import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected
+import xyz.driver.core.rest.ServiceRequestContext
+
+import scala.concurrent.Future
+import scala.util.{Failure, Success}
+import scalaz.OptionT
+
+object auth {
+
+ sealed trait Permission
+ case object CanSeeUser extends Permission
+ case object CanSeeAssay extends Permission
+ case object CanSeeReport extends Permission
+ case object CanCreateReport extends Permission
+ case object CanEditReport extends Permission
+ case object CanReviewReport extends Permission
+ case object CanEditReviewingReport extends Permission
+ case object CanSignOutReport extends Permission
+ case object CanAmendReport extends Permission
+ case object CanShareReportWithPatient extends Permission
+ case object CanAssignRoles extends Permission
+
+ trait Role {
+ val id: Id[Role]
+ val name: Name[Role]
+ val permissions: Set[Permission]
+
+ def hasPermission(permission: Permission): Boolean = permissions.contains(permission)
+ }
+
+ case object ObserverRole extends Role {
+ val id = Id("1")
+ val name = Name("observer")
+ val permissions = Set[Permission](CanSeeUser, CanSeeAssay, CanSeeReport)
+ }
+
+ case object PatientRole extends Role {
+ val id = Id("2")
+ val name = Name("patient")
+ val permissions = Set.empty[Permission]
+ }
+
+ case object CuratorRole extends Role {
+ val id = Id("3")
+ val name = Name("curator")
+ val permissions = ObserverRole.permissions ++ Set[Permission](CanEditReport, CanReviewReport)
+ }
+
+ case object PathologistRole extends Role {
+ val id = Id("4")
+ val name = Name("pathologist")
+ val permissions = ObserverRole.permissions ++
+ Set[Permission](CanEditReport, CanSignOutReport, CanAmendReport, CanEditReviewingReport)
+ }
+
+ case object AdministratorRole extends Role {
+ val id = Id("5")
+ val name = Name("administrator")
+ val permissions = CuratorRole.permissions ++
+ Set[Permission](CanCreateReport, CanShareReportWithPatient, CanAssignRoles)
+ }
+
+ case object PhysicianRole extends Role {
+ val id = Id("6")
+ val name = Name("physician")
+ val permissions = Set[Permission]()
+ }
+
+ case object RelativeRole extends Role {
+ val id = Id("7")
+ val name = Name("relative")
+ val permissions = Set[Permission]()
+ }
+
+ trait User {
+ def id: Id[User]
+ def roles: Set[Role]
+ def permissions: Set[Permission] = roles.flatMap(_.permissions)
+ }
+
+ final case class AuthToken(value: String)
+
+ final case class PasswordHash(value: String)
+
+ object AuthService {
+ val AuthenticationTokenHeader = rest.ContextHeaders.AuthenticationTokenHeader
+ val SetAuthenticationTokenHeader = "set-authorization"
+ }
+
+ trait AuthService[U <: User] {
+
+ import akka.http.scaladsl.server._
+ import Directives._
+
+ protected def authStatus(context: ServiceRequestContext): OptionT[Future, U]
+
+ def authorize(permissions: Permission*): Directive1[U] = {
+ rest.serviceContext flatMap { ctx =>
+ onComplete(authStatus(ctx).run).flatMap {
+ case Success(Some(user)) =>
+ if (permissions.forall(user.permissions.contains)) provide(user)
+ else {
+ val challenge =
+ HttpChallenges.basic(s"User does not have the required permissions: ${permissions.mkString(", ")}")
+ reject(AuthenticationFailedRejection(CredentialsRejected, challenge))
+ }
+
+ case Success(None) =>
+ reject(ValidationRejection(s"Wasn't able to find authenticated user for the token provided"))
+
+ case Failure(t) =>
+ reject(ValidationRejection(s"Wasn't able to verify token for authenticated user", Some(t)))
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/config.scala b/src/main/scala/xyz/driver/core/config.scala
index 29cd9ed..112986e 100644
--- a/src/main/scala/com/drivergrp/core/config.scala
+++ b/src/main/scala/xyz/driver/core/config.scala
@@ -1,4 +1,4 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.io.File
import com.typesafe.config.{Config, ConfigFactory}
diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala
new file mode 100644
index 0000000..8ae9122
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/core.scala
@@ -0,0 +1,52 @@
+package xyz.driver
+
+import scalaz.Equal
+
+package object core {
+
+ import scala.language.reflectiveCalls
+
+ def make[T](v: => T)(f: T => Unit): T = {
+ val value = v
+ f(value)
+ value
+ }
+
+ def using[R <: { def close() }, P](r: => R)(f: R => P): P = {
+ val resource = r
+ try {
+ f(resource)
+ } finally {
+ resource.close()
+ }
+ }
+}
+
+package core {
+
+ final case class Id[+Tag](value: String) extends AnyVal {
+ @inline def length: Int = value.length
+ override def toString: String = value
+ }
+
+ 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)
+ }
+
+ final case class Name[+Tag](value: String) extends AnyVal {
+ @inline def length: Int = value.length
+ override def toString: String = value
+ }
+
+ object Name {
+ implicit def nameEqual[T]: Equal[Name[T]] = Equal.equal[Name[T]](_ == _)
+ implicit def nameOrdering[T]: Ordering[Name[T]] = Ordering.by(_.value)
+ }
+
+ object revision {
+ final case class Revision[T](id: String)
+
+ implicit def revisionEqual[T]: Equal[Revision[T]] = Equal.equal[Revision[T]](_.id == _.id)
+ }
+}
diff --git a/src/main/scala/xyz/driver/core/database.scala b/src/main/scala/xyz/driver/core/database.scala
new file mode 100644
index 0000000..a82e345
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/database.scala
@@ -0,0 +1,125 @@
+package xyz.driver.core
+
+import slick.backend.DatabaseConfig
+import slick.dbio.{DBIOAction, NoStream}
+import slick.driver.JdbcProfile
+import xyz.driver.core.time.Time
+
+import scala.concurrent.{ExecutionContext, Future}
+import scalaz.Monad
+
+object database {
+
+ trait Database {
+ val profile: JdbcProfile
+ val database: JdbcProfile#Backend#Database
+ }
+
+ object Database {
+
+ def fromConfig(databaseName: String): Database = {
+ val dbConfig: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig(databaseName)
+
+ new Database {
+ val profile: JdbcProfile = dbConfig.driver
+ val database: JdbcProfile#Backend#Database = dbConfig.db
+ }
+ }
+ }
+
+ 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._
+
+ implicit def `xyz.driver.core.Id.columnType`[T]: BaseColumnType[Id[T]]
+
+ implicit def `xyz.driver.core.Name.columnType`[T]: BaseColumnType[Name[T]] =
+ MappedColumnType.base[Name[T], String](_.value, Name[T](_))
+
+ implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time] =
+ MappedColumnType.base[Time, Long](_.millis, Time(_))
+ }
+
+ object ColumnTypes {
+ trait UUID extends ColumnTypes {
+ 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))
+ }
+ trait SerialId extends ColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.Id.columnType`[T] =
+ MappedColumnType.base[Id[T], Long](_.value.toLong, serialId => Id[T](serialId.toString))
+ }
+ trait NaturalId extends ColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.Id.columnType`[T] =
+ MappedColumnType.base[Id[T], String](_.value, Id[T](_))
+ }
+ }
+
+ trait DatabaseObject extends ColumnTypes {
+
+ def createTables(): Future[Unit]
+ def disconnect(): Unit
+ }
+
+ abstract class DatabaseObjectAdapter extends DatabaseObject {
+ 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/com/drivergrp/core/file.scala b/src/main/scala/xyz/driver/core/file.scala
index 20bd36e..9cea9e5 100644
--- a/src/main/scala/com/drivergrp/core/file.scala
+++ b/src/main/scala/xyz/driver/core/file.scala
@@ -1,4 +1,4 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.io.File
import java.nio.file.{Path, Paths}
@@ -6,8 +6,8 @@ import java.util.UUID._
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model.{Bucket, GetObjectRequest, ListObjectsV2Request}
-import com.drivergrp.core.revision.Revision
-import com.drivergrp.core.time.Time
+import xyz.driver.core.revision.Revision
+import xyz.driver.core.time.Time
import scala.concurrent.{ExecutionContext, Future}
import scalaz.{ListT, OptionT}
@@ -15,10 +15,11 @@ import scalaz.{ListT, OptionT}
object file {
final case class FileLink(
- name: Name[File],
- location: Path,
- revision: Revision[File],
- lastModificationDate: Time
+ name: Name[File],
+ location: Path,
+ revision: Revision[File],
+ lastModificationDate: Time,
+ fileSize: Long
)
trait FileService {
@@ -58,7 +59,7 @@ object file {
def upload(localSource: File, destination: Path): Future[Unit] = Future {
checkSafeFileName(destination) {
- val _ = s3.putObject(bucket, destination.toString, localSource).getETag
+ val _ = s3.putObject(bucket.value, destination.toString, localSource).getETag
}
}
@@ -71,20 +72,20 @@ object file {
if (!tempDestinationFile.getParentFile.mkdirs()) {
throw new Exception(s"Failed to create temp directory to download file `$tempDestinationFile`")
} else {
- Option(s3.getObject(new GetObjectRequest(bucket, filePath.toString), tempDestinationFile)).map { _ =>
+ Option(s3.getObject(new GetObjectRequest(bucket.value, filePath.toString), tempDestinationFile)).map { _ =>
tempDestinationFile
}
}
})
def delete(filePath: Path): Future[Unit] = Future {
- s3.deleteObject(bucket, filePath.toString)
+ s3.deleteObject(bucket.value, filePath.toString)
}
def list(path: Path): ListT[Future, FileLink] =
ListT.listT(Future {
import scala.collection.JavaConverters._
- val req = new ListObjectsV2Request().withBucketName(bucket).withPrefix(path.toString).withMaxKeys(2)
+ val req = new ListObjectsV2Request().withBucketName(bucket.value).withPrefix(path.toString).withMaxKeys(2)
def isInSubFolder(path: Path)(fileLink: FileLink) =
fileLink.location.toString.replace(path.toString + "/", "").contains("/")
@@ -97,7 +98,8 @@ object file {
FileLink(Name[File](summary.getKey),
Paths.get(path.toString + "/" + summary.getKey),
Revision[File](summary.getETag),
- Time(summary.getLastModified.getTime))
+ Time(summary.getLastModified.getTime),
+ summary.getSize)
} filterNot isInSubFolder(path)
} toList
})
@@ -114,7 +116,7 @@ object file {
if (localSource.renameTo(destinationFile)) ()
else {
throw new Exception(
- s"Failed to move file from `${localSource.getCanonicalPath}` to `${destinationFile.getCanonicalPath}`")
+ s"Failed to move file from `${localSource.getCanonicalPath}` to `${destinationFile.getCanonicalPath}`")
}
} else {
throw new Exception(s"Failed to create parent directories for file `${destinationFile.getCanonicalPath}`")
@@ -143,7 +145,8 @@ object file {
FileLink(Name[File](file.getName),
Paths.get(file.getPath),
Revision[File](file.hashCode.toString),
- Time(file.lastModified()))
+ Time(file.lastModified()),
+ file.length())
}
} else List.empty[FileLink]
})
diff --git a/src/main/scala/com/drivergrp/core/generators.scala b/src/main/scala/xyz/driver/core/generators.scala
index 10df7db..c61cb94 100644
--- a/src/main/scala/com/drivergrp/core/generators.scala
+++ b/src/main/scala/xyz/driver/core/generators.scala
@@ -1,9 +1,9 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.math.MathContext
-import com.drivergrp.core.revision.Revision
-import com.drivergrp.core.time.{Time, TimeRange}
+import xyz.driver.core.revision.Revision
+import xyz.driver.core.time.{Time, TimeRange}
import scala.reflect.ClassTag
import scala.util.Random
@@ -13,12 +13,22 @@ object generators {
private val random = new Random
import random._
- private val DefaultMaxLength = 100
+ private val DefaultMaxLength = 10
private val StringLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ".toSet
- def nextId[T](): Id[T] = Id[T](scala.math.abs(nextLong()))
+ def nextInt(maxValue: Int): Int = random.nextInt(maxValue)
- def nextId[T](maxValue: Int): Id[T] = Id[T](scala.math.abs(nextInt(maxValue).toLong))
+ def nextBoolean(): Boolean = random.nextBoolean()
+
+ def nextDouble(): Double = random.nextDouble()
+
+ def nextId[T](): Id[T] = Id[T](nextUuid().toString)
+
+ def nextId[T](maxLength: Int): Id[T] = Id[T](nextString(maxLength))
+
+ def nextNumericId[T](): Id[T] = Id[T](nextLong.abs.toString)
+
+ def nextNumericId[T](maxValue: Int): Id[T] = Id[T](nextInt(maxValue).toString)
def nextName[T](maxLength: Int = DefaultMaxLength): Name[T] = Name[T](nextString(maxLength))
@@ -29,7 +39,7 @@ object generators {
def nextString(maxLength: Int = DefaultMaxLength): String =
(oneOf[Char](StringLetters) +: arrayOf(oneOf[Char](StringLetters), maxLength - 1)).mkString
- def nextOption[T](value: => T): Option[T] = if (nextBoolean) Option(value) else None
+ def nextOption[T](value: => T): Option[T] = if (nextBoolean()) Option(value) else None
def nextPair[L, R](left: => L, right: => R): (L, R) = (left, right)
diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala
new file mode 100644
index 0000000..277543b
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/json.scala
@@ -0,0 +1,158 @@
+package xyz.driver.core
+
+import akka.http.scaladsl.model.Uri.Path
+import akka.http.scaladsl.server.PathMatcher.{Matched, Unmatched}
+import akka.http.scaladsl.server.{PathMatcher, _}
+import akka.http.scaladsl.unmarshalling.Unmarshaller
+import spray.json.{DeserializationException, JsNumber, _}
+import xyz.driver.core.revision.Revision
+import xyz.driver.core.time.Time
+
+import scala.reflect.runtime.universe._
+
+object json {
+
+ 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)))
+ case _ => Unmatched
+ }
+ }
+
+ implicit def idFormat[T] = new RootJsonFormat[Id[T]] {
+ def write(id: Id[T]) = JsString(id.value)
+
+ def read(value: JsValue) = value match {
+ case JsString(id) => Id[T](id)
+ case _ => throw DeserializationException("Id expects string")
+ }
+ }
+
+ 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)))
+ case _ => Unmatched
+ }
+ }
+
+ implicit def nameFormat[T] = new RootJsonFormat[Name[T]] {
+ def write(name: Name[T]) = JsString(name.value)
+
+ def read(value: JsValue): Name[T] = value match {
+ case JsString(name) => Name[T](name)
+ case _ => throw DeserializationException("Name expects string")
+ }
+ }
+
+ def TimeInPath: PathMatcher1[Time] =
+ PathMatcher("""[+-]?\d*""".r) flatMap { string =>
+ try Some(Time(string.toLong))
+ catch { case _: IllegalArgumentException => None }
+ }
+
+ implicit val timeFormat = new RootJsonFormat[Time] {
+ def write(time: Time) = JsObject("timestamp" -> JsNumber(time.millis))
+
+ def read(value: JsValue): Time = value match {
+ case JsObject(fields) =>
+ fields
+ .get("timestamp")
+ .flatMap {
+ case JsNumber(millis) => Some(Time(millis.toLong))
+ case _ => None
+ }
+ .getOrElse(throw DeserializationException("Time expects number"))
+ case _ => throw DeserializationException("Time expects number")
+ }
+ }
+
+ 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))
+ }
+
+ implicit def revisionFromStringUnmarshaller[T]: Unmarshaller[String, Revision[T]] =
+ Unmarshaller.strict[String, Revision[T]](Revision[T](_))
+
+ implicit def revisionFormat[T] = new RootJsonFormat[Revision[T]] {
+ def write(revision: Revision[T]) = JsString(revision.id.toString)
+
+ def read(value: JsValue): Revision[T] = value match {
+ case JsString(revision) => Revision[T](revision)
+ case _ => throw DeserializationException("Revision expects uuid string")
+ }
+ }
+
+ class EnumJsonFormat[T](mapping: (String, T)*) extends RootJsonFormat[T] {
+ private val map = mapping.toMap
+
+ override def write(value: T): JsValue = {
+ map.find(_._2 == value).map(_._1) match {
+ case Some(name) => JsString(name)
+ case _ => serializationError(s"Value $value is not found in the mapping $map")
+ }
+ }
+
+ override def read(json: JsValue): T = json match {
+ case JsString(name) =>
+ map.getOrElse(name, throw DeserializationException(s"Value $name is not found in the mapping $map"))
+ case _ => deserializationError("Expected string as enumeration value, but got " + json)
+ }
+ }
+
+ class ValueClassFormat[T: TypeTag](writeValue: T => BigDecimal, create: BigDecimal => T) extends JsonFormat[T] {
+ def write(valueClass: T) = JsNumber(writeValue(valueClass))
+ def read(json: JsValue): T = json match {
+ case JsNumber(value) => create(value)
+ case _ => deserializationError(s"Expected number as ${typeOf[T].getClass.getName}, but got " + json)
+ }
+ }
+
+ class GadtJsonFormat[T: TypeTag](typeField: String,
+ typeValue: PartialFunction[T, String],
+ jsonFormat: PartialFunction[String, JsonFormat[_ <: T]])
+ extends RootJsonFormat[T] {
+
+ def write(value: T): JsValue = {
+
+ val valueType = typeValue.applyOrElse(value, { v: T =>
+ deserializationError(s"No Value type for this type of ${typeOf[T].getClass.getName}: " + v)
+ })
+
+ val valueFormat =
+ jsonFormat.applyOrElse(valueType, { f: String =>
+ deserializationError(s"No Json format for this type of $valueType")
+ })
+
+ valueFormat.asInstanceOf[JsonFormat[T]].write(value) match {
+ case JsObject(fields) => JsObject(fields ++ Map(typeField -> JsString(valueType)))
+ case _ => serializationError(s"${typeOf[T].getClass.getName} serialized not to a JSON object")
+ }
+ }
+
+ def read(json: JsValue): T = json match {
+ case JsObject(fields) =>
+ val valueJson = JsObject(fields.filterNot(_._1 == typeField))
+ fields(typeField) match {
+ case JsString(valueType) =>
+ val valueFormat = jsonFormat.applyOrElse(valueType, { t: String =>
+ deserializationError(s"Unknown ${typeOf[T].getClass.getName} type ${fields(typeField)}")
+ })
+ valueFormat.read(valueJson)
+ case _ =>
+ deserializationError(s"Unknown ${typeOf[T].getClass.getName} type ${fields(typeField)}")
+ }
+ case _ =>
+ deserializationError(s"Expected Json Object as ${typeOf[T].getClass.getName}, but got " + json)
+ }
+ }
+
+ object GadtJsonFormat {
+
+ def create[T: TypeTag](typeField: String)(typeValue: PartialFunction[T, String])(
+ jsonFormat: PartialFunction[String, JsonFormat[_ <: T]]) = {
+
+ new GadtJsonFormat[T](typeField, typeValue, jsonFormat)
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/logging.scala b/src/main/scala/xyz/driver/core/logging.scala
index 126c670..ba17131 100644
--- a/src/main/scala/com/drivergrp/core/logging.scala
+++ b/src/main/scala/xyz/driver/core/logging.scala
@@ -1,5 +1,11 @@
-package com.drivergrp.core
+package xyz.driver.core
+import java.text.SimpleDateFormat
+import java.util.Date
+
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.LayoutBase
+import org.apache.commons.lang3.StringUtils
import org.slf4j.Marker
object logging {
@@ -44,36 +50,44 @@ object logging {
*/
class TypesafeScalaLogger(scalaLogging: com.typesafe.scalalogging.Logger) extends Logger {
- def fatal(message: String): Unit = scalaLogging.error(message)
- def fatal(message: String, cause: Throwable): Unit = scalaLogging.error(message, cause)
- def fatal(message: String, args: AnyRef*): Unit = scalaLogging.error(message, args)
- def fatal(marker: Marker, message: String): Unit = scalaLogging.error(marker, message)
- def fatal(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.error(marker, message, cause)
- def fatal(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.error(marker, message, args)
-
- def error(message: String): Unit = scalaLogging.warn(message)
- def error(message: String, cause: Throwable): Unit = scalaLogging.warn(message, cause)
- def error(message: String, args: AnyRef*): Unit = scalaLogging.warn(message, args)
- def error(marker: Marker, message: String): Unit = scalaLogging.warn(marker, message)
- def error(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.warn(marker, message, cause)
- def error(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.warn(marker, message, args)
-
- def audit(message: String): Unit = scalaLogging.info(message)
- def audit(message: String, cause: Throwable): Unit = scalaLogging.info(message, cause)
- def audit(message: String, args: AnyRef*): Unit = scalaLogging.info(message, args)
- def audit(marker: Marker, message: String): Unit = scalaLogging.info(marker, message)
- def audit(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.info(marker, message, cause)
- def audit(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.info(marker, message, args)
-
- def debug(message: String): Unit = scalaLogging.debug(message)
- def debug(message: String, cause: Throwable): Unit = scalaLogging.debug(message, cause)
- def debug(message: String, args: AnyRef*): Unit = scalaLogging.debug(message, args)
- def debug(marker: Marker, message: String): Unit = scalaLogging.debug(marker, message)
- def debug(marker: Marker, message: String, cause: Throwable): Unit = scalaLogging.debug(marker, message, cause)
- def debug(marker: Marker, message: String, args: AnyRef*): Unit = scalaLogging.debug(marker, message, args)
+ def fatal(message: String): Unit = scalaLogging.error("FATAL " + message)
+ def fatal(message: String, cause: Throwable): Unit = scalaLogging.error("FATAL " + message, cause)
+ def fatal(message: String, args: AnyRef*): Unit = scalaLogging.error("FATAL " + message, args)
+ def fatal(marker: Marker, message: String): Unit = scalaLogging.error(marker, "FATAL " + message)
+ def fatal(marker: Marker, message: String, cause: Throwable): Unit =
+ scalaLogging.error(marker, "FATAL " + message, cause)
+ def fatal(marker: Marker, message: String, args: AnyRef*): Unit =
+ scalaLogging.error(marker, "FATAL " + message, args)
+
+ def error(message: String): Unit = scalaLogging.warn("ERROR " + message)
+ def error(message: String, cause: Throwable): Unit = scalaLogging.warn("ERROR " + message, cause)
+ def error(message: String, args: AnyRef*): Unit = scalaLogging.warn("ERROR " + message, args)
+ def error(marker: Marker, message: String): Unit = scalaLogging.warn(marker, "ERROR " + message)
+ def error(marker: Marker, message: String, cause: Throwable): Unit =
+ scalaLogging.warn(marker, "ERROR " + message, cause)
+ def error(marker: Marker, message: String, args: AnyRef*): Unit =
+ scalaLogging.warn(marker, "ERROR " + message, args)
+
+ def audit(message: String): Unit = scalaLogging.info("AUDIT " + message)
+ def audit(message: String, cause: Throwable): Unit = scalaLogging.info("AUDIT " + message, cause)
+ def audit(message: String, args: AnyRef*): Unit = scalaLogging.info("AUDIT " + message, args)
+ def audit(marker: Marker, message: String): Unit = scalaLogging.info(marker, "AUDIT " + message)
+ def audit(marker: Marker, message: String, cause: Throwable): Unit =
+ scalaLogging.info(marker, "AUDIT " + message, cause)
+ def audit(marker: Marker, message: String, args: AnyRef*): Unit =
+ scalaLogging.info(marker, "AUDIT " + message, args)
+
+ def debug(message: String): Unit = scalaLogging.debug("DEBUG " + message)
+ def debug(message: String, cause: Throwable): Unit = scalaLogging.debug("DEBUG " + message, cause)
+ def debug(message: String, args: AnyRef*): Unit = scalaLogging.debug("DEBUG " + message, args)
+ def debug(marker: Marker, message: String): Unit = scalaLogging.debug(marker, "DEBUG " + message)
+ def debug(marker: Marker, message: String, cause: Throwable): Unit =
+ scalaLogging.debug(marker, "DEBUG " + message, cause)
+ def debug(marker: Marker, message: String, args: AnyRef*): Unit =
+ scalaLogging.debug(marker, "DEBUG " + message, args)
}
- class NoLogger() extends Logger {
+ object NoLogger extends Logger {
def fatal(message: String): Unit = {}
def fatal(message: String, cause: Throwable): Unit = {}
@@ -103,4 +117,53 @@ object logging {
def debug(marker: Marker, message: String, cause: Throwable): Unit = {}
def debug(marker: Marker, message: String, args: AnyRef*): Unit = {}
}
+
+ class DriverLayout extends LayoutBase[ILoggingEvent] {
+ import scala.collection.JavaConverters._
+
+ private val FieldSeparator = "="
+ private val DateFormatString = "MM/dd/yyyy HH:mm:ss"
+ private val newline = System.getProperty("line.separator")
+ private val IgnoredClassesInStack = Set("org.apache.catalina", "org.apache.coyote", "sun.reflect", "javax.servlet")
+
+ override def doLayout(loggingEvent: ILoggingEvent): String = {
+
+ val date = new SimpleDateFormat(DateFormatString).format(new Date(loggingEvent.getTimeStamp))
+ val level = StringUtils.rightPad(loggingEvent.getLevel.toString, 5)
+
+ val message = new StringBuilder(s"$date [$level] - loggingEvent.getMessage$newline")
+
+ logContext(message, loggingEvent)
+
+ Option(loggingEvent.getCallerData) foreach { stacktrace =>
+ val stacktraceLength = stacktrace.length
+
+ if (stacktraceLength > 0) {
+ val location = stacktrace.head
+
+ val _ = message
+ .append(s"Location: ${location.getClassName}.${location.getMethodName}:${location.getLineNumber}$newline")
+ .append(s"Exception: ${location.toString}$newline")
+
+ if (stacktraceLength > 1) {
+ message.append(stacktrace.tail.filterNot { e =>
+ IgnoredClassesInStack.forall(ignored => !e.getClassName.startsWith(ignored))
+ } map {
+ _.toString
+ } mkString newline)
+ }
+ }
+ }
+
+ message.toString
+ }
+
+ private def logContext(message: StringBuilder, loggingEvent: ILoggingEvent) = {
+ Option(loggingEvent.getMDCPropertyMap).map(_.asScala).filter(_.nonEmpty).foreach { context =>
+ message.append(
+ context map { case (key, value) => s"$key$FieldSeparator$value" } mkString ("Context: ", " ", newline)
+ )
+ }
+ }
+ }
}
diff --git a/src/main/scala/com/drivergrp/core/messages.scala b/src/main/scala/xyz/driver/core/messages.scala
index 3a97401..94d9889 100644
--- a/src/main/scala/com/drivergrp/core/messages.scala
+++ b/src/main/scala/xyz/driver/core/messages.scala
@@ -1,9 +1,9 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.util.Locale
-import com.drivergrp.core.logging.Logger
import com.typesafe.config.Config
+import xyz.driver.core.logging.Logger
import scala.collection.JavaConverters._
diff --git a/src/main/scala/com/drivergrp/core/rest.scala b/src/main/scala/xyz/driver/core/rest.scala
index d97e13e..f05a800 100644
--- a/src/main/scala/com/drivergrp/core/rest.scala
+++ b/src/main/scala/xyz/driver/core/rest.scala
@@ -1,4 +1,4 @@
-package com.drivergrp.core
+package xyz.driver.core
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
@@ -6,29 +6,63 @@ import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
-import akka.stream.scaladsl.Flow
-import akka.util.ByteString
-import com.drivergrp.core.auth.{AuthService, AuthToken}
-import com.drivergrp.core.crypto.Crypto
-import com.drivergrp.core.logging.Logger
-import com.drivergrp.core.stats.Stats
-import com.drivergrp.core.time.TimeRange
-import com.drivergrp.core.time.provider.TimeProvider
import com.github.swagger.akka.model._
import com.github.swagger.akka.{HasActorSystem, SwaggerHttpService}
import com.typesafe.config.Config
+import xyz.driver.core.logging.Logger
+import xyz.driver.core.stats.Stats
+import xyz.driver.core.time.TimeRange
+import xyz.driver.core.time.provider.TimeProvider
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
-import scalaz.{Failure => _, Success => _}
+import scalaz.Scalaz.{Id => _, _}
object rest {
+ object ContextHeaders {
+ val AuthenticationTokenHeader = "WWW-Authenticate"
+ val TrackingIdHeader = "X-Trace"
+
+ object LinkerD {
+ // https://linkerd.io/doc/0.7.4/linkerd/protocol-http/
+ def isLinkerD(headerName: String) = headerName.startsWith("l5d-")
+ }
+ }
+
+ final case class ServiceRequestContext(
+ trackingId: String = generators.nextUuid().toString,
+ contextHeaders: Map[String, String] = Map.empty[String, String])
+
+ import akka.http.scaladsl.server._
+ import Directives._
+
+ def serviceContext: Directive1[ServiceRequestContext] = extract(ctx => extractServiceContext(ctx))
+
+ def extractServiceContext(ctx: RequestContext): ServiceRequestContext =
+ ServiceRequestContext(extractTrackingId(ctx), extractContextHeaders(ctx))
+
+ def extractTrackingId(ctx: RequestContext): String = {
+ ctx.request.headers
+ .find(_.name == ContextHeaders.TrackingIdHeader)
+ .fold(java.util.UUID.randomUUID.toString)(_.value())
+ }
+
+ def extractContextHeaders(ctx: RequestContext): Map[String, String] = {
+ ctx.request.headers.filter { h =>
+ h.name === ContextHeaders.AuthenticationTokenHeader || h.name === ContextHeaders.TrackingIdHeader
+ // || ContextHeaders.LinkerD.isLinkerD(h.lowercaseName)
+ } map { header =>
+ header.name -> header.value
+ } toMap
+ }
+
+
trait Service
trait ServiceTransport {
- def sendRequest(authToken: AuthToken)(requestStub: HttpRequest): Future[Unmarshal[ResponseEntity]]
+ def sendRequest(context: ServiceRequestContext)(requestStub: HttpRequest): Future[Unmarshal[ResponseEntity]]
}
trait ServiceDiscovery {
@@ -37,35 +71,28 @@ object rest {
}
class HttpRestServiceTransport(actorSystem: ActorSystem, executionContext: ExecutionContext,
- crypto: Crypto, log: Logger, stats: Stats, time: TimeProvider) extends ServiceTransport {
+ log: Logger, stats: Stats, time: TimeProvider) extends ServiceTransport {
protected implicit val materializer = ActorMaterializer()(actorSystem)
protected implicit val execution = executionContext
- def sendRequest(authToken: AuthToken)(requestStub: HttpRequest): Future[Unmarshal[ResponseEntity]] = {
+ def sendRequest(context: ServiceRequestContext)(requestStub: HttpRequest): Future[Unmarshal[ResponseEntity]] = {
val requestTime = time.currentTime()
- val encryptionFlow = Flow[ByteString] map { bytes =>
- ByteString(crypto.encrypt(crypto.keyForToken(authToken))(bytes.toArray))
- }
- val decryptionFlow = Flow[ByteString] map { bytes =>
- ByteString(crypto.decrypt(crypto.keyForToken(authToken))(bytes.toArray))
- }
val request = requestStub
- .withEntity(requestStub.entity.transformDataBytes(encryptionFlow))
- .withHeaders(
- RawHeader(AuthService.AuthenticationTokenHeader, authToken.value.value))
+ .withHeaders(RawHeader(ContextHeaders.TrackingIdHeader, context.trackingId))
+ .withHeaders(context.contextHeaders.toSeq.map { h => RawHeader(h._1, h._2): HttpHeader }: _*)
- log.audit(s"Sending to ${request.uri} request $request")
+ log.audit(s"Sending to ${request.uri} request $request with tracking id ${context.trackingId}")
val responseEntity = Http()(actorSystem).singleRequest(request)(materializer) map { response =>
if(response.status == StatusCodes.NotFound) {
Unmarshal(HttpEntity.Empty: ResponseEntity)
} else if(response.status.isFailure()) {
- throw new Exception("Http status is failure " + response.status)
+ throw new Exception(s"Http status is failure ${response.status}")
} else {
- Unmarshal(response.entity.transformDataBytes(decryptionFlow))
+ Unmarshal(response.entity)
}
}
diff --git a/src/main/scala/xyz/driver/core/stats.scala b/src/main/scala/xyz/driver/core/stats.scala
new file mode 100644
index 0000000..5759012
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/stats.scala
@@ -0,0 +1,97 @@
+package xyz.driver.core
+
+import java.io.File
+import java.lang.management.ManagementFactory
+import java.lang.reflect.Modifier
+
+import xyz.driver.core.logging.Logger
+import xyz.driver.core.time.{Time, TimeRange}
+
+object stats {
+
+ type StatsKey = String
+ type StatsKeys = Seq[StatsKey]
+
+ trait Stats {
+
+ def recordStats(keys: StatsKeys, interval: TimeRange, value: BigDecimal): Unit
+
+ def recordStats(keys: StatsKeys, interval: TimeRange, value: Int): Unit =
+ recordStats(keys, interval, BigDecimal(value))
+
+ def recordStats(key: StatsKey, interval: TimeRange, value: BigDecimal): Unit =
+ recordStats(Vector(key), interval, value)
+
+ def recordStats(key: StatsKey, interval: TimeRange, value: Int): Unit =
+ recordStats(Vector(key), interval, BigDecimal(value))
+
+ def recordStats(keys: StatsKeys, time: Time, value: BigDecimal): Unit =
+ recordStats(keys, TimeRange(time, time), value)
+
+ def recordStats(keys: StatsKeys, time: Time, value: Int): Unit =
+ recordStats(keys, TimeRange(time, time), BigDecimal(value))
+
+ def recordStats(key: StatsKey, time: Time, value: BigDecimal): Unit =
+ recordStats(Vector(key), TimeRange(time, time), value)
+
+ def recordStats(key: StatsKey, time: Time, value: Int): Unit =
+ recordStats(Vector(key), TimeRange(time, time), BigDecimal(value))
+ }
+
+ class LogStats(log: Logger) extends Stats {
+ def recordStats(keys: StatsKeys, interval: TimeRange, value: BigDecimal): Unit = {
+ val valueString = value.bigDecimal.toPlainString
+ log.audit(s"${keys.mkString(".")}(${interval.start.millis}-${interval.end.millis})=$valueString")
+ }
+ }
+
+ final case class MemoryStats(free: Long, total: Long, max: Long)
+
+ final case class GarbageCollectorStats(totalGarbageCollections: Long, garbageCollectionTime: Long)
+
+ final case class FileRootSpace(path: String, totalSpace: Long, freeSpace: Long, usableSpace: Long)
+
+ object SystemStats {
+
+ def memoryUsage: MemoryStats = {
+ val runtime = Runtime.getRuntime
+ MemoryStats(runtime.freeMemory, runtime.totalMemory, runtime.maxMemory)
+ }
+
+ def availableProcessors: Int = {
+ Runtime.getRuntime.availableProcessors()
+ }
+
+ def garbageCollectorStats: GarbageCollectorStats = {
+ import scala.collection.JavaConverters._
+
+ val (totalGarbageCollections, garbageCollectionTime) =
+ ManagementFactory.getGarbageCollectorMXBeans.asScala.foldLeft(0L -> 0L) {
+ case ((total, collectionTime), gc) =>
+ (total + math.max(0L, gc.getCollectionCount)) -> (collectionTime + math.max(0L, gc.getCollectionTime))
+ }
+
+ GarbageCollectorStats(totalGarbageCollections, garbageCollectionTime)
+ }
+
+ def fileSystemSpace: Array[FileRootSpace] = {
+ File.listRoots() map { root =>
+ FileRootSpace(root.getAbsolutePath, root.getTotalSpace, root.getFreeSpace, root.getUsableSpace)
+ }
+ }
+
+ def operatingSystemStats: Map[String, String] = {
+ val operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean
+ operatingSystemMXBean.getClass.getDeclaredMethods
+ .map(method => { method.setAccessible(true); method })
+ .filter(method => method.getName.startsWith("get") && Modifier.isPublic(method.getModifiers))
+ .map { method =>
+ try {
+ method.getName -> String.valueOf(method.invoke(operatingSystemMXBean))
+ } catch {
+ case t: Throwable => method.getName -> t.getMessage
+ }
+ } toMap
+ }
+ }
+}
diff --git a/src/main/scala/com/drivergrp/core/time.scala b/src/main/scala/xyz/driver/core/time.scala
index b935713..6ff8209 100644
--- a/src/main/scala/com/drivergrp/core/time.scala
+++ b/src/main/scala/xyz/driver/core/time.scala
@@ -1,4 +1,4 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.text.SimpleDateFormat
import java.util._
diff --git a/src/test/scala/com/drivergrp/core/AuthTest.scala b/src/test/scala/com/drivergrp/core/AuthTest.scala
deleted file mode 100644
index 42f9155..0000000
--- a/src/test/scala/com/drivergrp/core/AuthTest.scala
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.drivergrp.core
-
-import com.drivergrp.core.auth._
-import akka.http.scaladsl.testkit.ScalatestRouteTest
-import akka.http.scaladsl.server._
-import Directives._
-import akka.http.scaladsl.model.headers.{HttpChallenges, RawHeader}
-import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected
-import org.scalatest.mock.MockitoSugar
-import org.scalatest.{FlatSpec, Matchers}
-
-import scala.concurrent.Future
-import scalaz.OptionT
-
-class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRouteTest {
-
- val authStatusService: AuthService[User] = new AuthService[User] {
- override def authStatus(authToken: AuthToken): OptionT[Future, User] = OptionT.optionT[Future] {
- Future.successful(Some(new User() {
- override def id: Id[User] = Id[User](1L)
- override def roles: Set[Role] = Set(PathologistRole)
- }))
- }
- }
-
- import authStatusService._
-
- "'authorize' directive" should "throw error is auth token is not in the request" in {
-
- Get("/naive/attempt") ~>
- authorize(CanSignOutReport) {
- case (authToken, user) =>
- complete("Never going to be here")
- } ~>
- check {
- handled shouldBe false
- rejections should contain(MissingHeaderRejection("WWW-Authenticate"))
- }
- }
-
- it should "throw error is authorized user is not having the requested permission" in {
-
- val referenceAuthToken = AuthToken(Base64("I am a pathologist's token"))
-
- Post("/administration/attempt").addHeader(
- RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value)
- ) ~>
- authorize(CanAssignRoles) {
- case (authToken, user) =>
- complete("Never going to get here")
- } ~>
- check {
- handled shouldBe false
- rejections should contain(
- AuthenticationFailedRejection(
- CredentialsRejected,
- HttpChallenges.basic("User does not have the required permission CanAssignRoles")))
- }
- }
-
- it should "pass and retrieve the token to client code, if token is in request and user has permission" in {
-
- val referenceAuthToken = AuthToken(Base64("I am token"))
-
- Get("/valid/attempt/?a=2&b=5").addHeader(
- RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value)
- ) ~>
- authorize(CanSignOutReport) {
- case (authToken, user) =>
- complete("Alright, \"" + authToken.value.value + "\" is handled")
- } ~>
- check {
- handled shouldBe true
- responseAs[String] shouldBe "Alright, \"I am token\" is handled"
- }
- }
-}
diff --git a/src/test/scala/xyz/driver/core/AuthTest.scala b/src/test/scala/xyz/driver/core/AuthTest.scala
new file mode 100644
index 0000000..f4d4d2a
--- /dev/null
+++ b/src/test/scala/xyz/driver/core/AuthTest.scala
@@ -0,0 +1,79 @@
+package xyz.driver.core
+
+import akka.http.scaladsl.testkit.ScalatestRouteTest
+import akka.http.scaladsl.server._
+import Directives._
+import akka.http.scaladsl.model.headers.{HttpChallenges, RawHeader}
+import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected
+import org.scalatest.mock.MockitoSugar
+import org.scalatest.{FlatSpec, Matchers}
+import xyz.driver.core.auth._
+import xyz.driver.core.rest.ServiceRequestContext
+
+import scala.concurrent.Future
+import scalaz.OptionT
+
+class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRouteTest {
+
+ val authStatusService: AuthService[User] = new AuthService[User] {
+ override def authStatus(context: ServiceRequestContext): OptionT[Future, User] = OptionT.optionT[Future] {
+ if (context.contextHeaders.keySet.contains(AuthService.AuthenticationTokenHeader)) {
+ Future.successful(Some(new User {
+ override def id: Id[User] = Id[User]("1")
+ override def roles: Set[Role] = Set(PathologistRole)
+ }: User))
+ } else {
+ Future.successful(Option.empty[User])
+ }
+ }
+ }
+
+ import authStatusService._
+
+ "'authorize' directive" should "throw error is auth token is not in the request" in {
+
+ Get("/naive/attempt") ~>
+ authorize(CanSignOutReport) { user =>
+ complete("Never going to be here")
+ } ~>
+ check {
+ // handled shouldBe false
+ rejections should contain(ValidationRejection("Wasn't able to find authenticated user for the token provided"))
+ }
+ }
+
+ it should "throw error is authorized user is not having the requested permission" in {
+
+ val referenceAuthToken = AuthToken("I am a pathologist's token")
+
+ Post("/administration/attempt").addHeader(
+ RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value)
+ ) ~>
+ authorize(CanAssignRoles) { user =>
+ complete("Never going to get here")
+ } ~>
+ check {
+ handled shouldBe false
+ rejections should contain(
+ AuthenticationFailedRejection(
+ CredentialsRejected,
+ HttpChallenges.basic("User does not have the required permissions: CanAssignRoles")))
+ }
+ }
+
+ it should "pass and retrieve the token to client code, if token is in request and user has permission" in {
+
+ val referenceAuthToken = AuthToken("I am token")
+
+ Get("/valid/attempt/?a=2&b=5").addHeader(
+ RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value)
+ ) ~>
+ authorize(CanSignOutReport) { user =>
+ complete("Alright, user \"" + user.id + "\" is authorized")
+ } ~>
+ check {
+ handled shouldBe true
+ responseAs[String] shouldBe "Alright, user \"1\" is authorized"
+ }
+ }
+}
diff --git a/src/test/scala/com/drivergrp/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala
index 19e685c..3eb9eaa 100644
--- a/src/test/scala/com/drivergrp/core/CoreTest.scala
+++ b/src/test/scala/xyz/driver/core/CoreTest.scala
@@ -1,11 +1,11 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.io.ByteArrayOutputStream
-import com.drivergrp.core.revision.Revision
+import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FlatSpec, Matchers}
-import org.mockito.Mockito._
+import xyz.driver.core.revision.Revision
class CoreTest extends FlatSpec with Matchers with MockitoSugar {
@@ -31,12 +31,12 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar {
"Id" should "have equality and ordering working correctly" in {
- (Id[String](1234213L) === Id[String](1234213L)) should be(true)
- (Id[String](1234213L) === Id[String](213414L)) should be(false)
- (Id[String](213414L) === Id[String](1234213L)) 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)
- Seq(Id[String](4L), Id[String](3L), Id[String](2L), Id[String](1L)).sorted should contain
- theSameElementsInOrderAs(Seq(Id[String](1L), Id[String](2L), Id[String](3L), Id[String](4L)))
+ 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")))
}
"Name" should "have equality and ordering working correctly" in {
diff --git a/src/test/scala/com/drivergrp/core/FileTest.scala b/src/test/scala/xyz/driver/core/FileTest.scala
index 2c9c2c9..57af1c2 100644
--- a/src/test/scala/com/drivergrp/core/FileTest.scala
+++ b/src/test/scala/xyz/driver/core/FileTest.scala
@@ -1,15 +1,15 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.io.File
import java.nio.file.Paths
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model._
-import com.drivergrp.core.file.{FileSystemStorage, S3Storage}
+import org.mockito.Matchers._
+import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FlatSpec, Matchers}
-import org.mockito.Mockito._
-import org.mockito.Matchers._
+import xyz.driver.core.file.{FileSystemStorage, S3Storage}
import scala.concurrent.Await
import scala.concurrent.duration._
@@ -44,14 +44,14 @@ class FileTest extends FlatSpec with Matchers with MockitoSugar {
false, // after file is uploaded it contains this one file (one page)
false) // after file is deleted it is empty (zero pages) again
when(s3ResultsMock.getObjectSummaries).thenReturn(
- // before file created it is empty, `getObjectSummaries` is never called
- List[S3ObjectSummary](s3ObjectSummaryMock).asJava, // after file is uploaded it contains this one file
- List.empty[S3ObjectSummary].asJava) // after file is deleted it is empty again
+ // before file created it is empty, `getObjectSummaries` is never called
+ List[S3ObjectSummary](s3ObjectSummaryMock).asJava, // after file is uploaded it contains this one file
+ List.empty[S3ObjectSummary].asJava) // after file is deleted it is empty again
val s3ObjectMetadataMock = mock[ObjectMetadata]
val amazonS3Mock = mock[AmazonS3]
when(amazonS3Mock.listObjectsV2(any[ListObjectsV2Request]())).thenReturn(s3ResultsMock)
- when(amazonS3Mock.putObject(testBucket, testFilePath.toString, sourceTestFile)).thenReturn(s3PutMock)
+ when(amazonS3Mock.putObject(testBucket.value, testFilePath.toString, sourceTestFile)).thenReturn(s3PutMock)
when(amazonS3Mock.getObject(any[GetObjectRequest](), any[File]())).thenReturn(s3ObjectMetadataMock)
val s3Storage = new S3Storage(amazonS3Mock, testBucket, scala.concurrent.ExecutionContext.global)
diff --git a/src/test/scala/com/drivergrp/core/GeneratorsTest.scala b/src/test/scala/xyz/driver/core/GeneratorsTest.scala
index 631149e..4ec73ec 100644
--- a/src/test/scala/com/drivergrp/core/GeneratorsTest.scala
+++ b/src/test/scala/xyz/driver/core/GeneratorsTest.scala
@@ -1,4 +1,4 @@
-package com.drivergrp.core
+package xyz.driver.core
import org.scalatest.{Assertions, FlatSpec, Matchers}
@@ -11,25 +11,25 @@ class GeneratorsTest extends FlatSpec with Matchers with Assertions {
val generatedId2 = nextId[String]()
val generatedId3 = nextId[Long]()
- generatedId1 should be >= 0L
- generatedId2 should be >= 0L
- generatedId3 should be >= 0L
+ generatedId1.length should be >= 0
+ generatedId2.length should be >= 0
+ generatedId3.length should be >= 0
generatedId1 should not be generatedId2
generatedId2 should !==(generatedId3)
}
it should "be able to generate com.drivergrp.core.Id identifiers with max value" in {
- val generatedLimitedId1 = nextId[String](10000)
- val generatedLimitedId2 = nextId[String](1000)
- val generatedLimitedId3 = nextId[Long](2000)
+ val generatedLimitedId1 = nextId[String](5)
+ val generatedLimitedId2 = nextId[String](4)
+ val generatedLimitedId3 = nextId[Long](3)
- generatedLimitedId1 should be >= 0L
- generatedLimitedId1 should be < 10000L
- generatedLimitedId2 should be >= 0L
- generatedLimitedId2 should be < 1000L
- generatedLimitedId3 should be >= 0L
- generatedLimitedId3 should be < 2000L
+ generatedLimitedId1.length should be >= 0
+ generatedLimitedId1.length should be < 6
+ generatedLimitedId2.length should be >= 0
+ generatedLimitedId2.length should be < 5
+ generatedLimitedId3.length should be >= 0
+ generatedLimitedId3.length should be < 4
generatedLimitedId1 should not be generatedLimitedId2
generatedLimitedId2 should !==(generatedLimitedId3)
}
@@ -37,11 +37,11 @@ class GeneratorsTest extends FlatSpec with Matchers with Assertions {
it should "be able to generate com.drivergrp.core.Name names" in {
nextName[String]() should not be nextName[String]()
- nextName[String]().length should be >= 0
+ nextName[String]().value.length should be >= 0
val fixedLengthName = nextName[String](10)
fixedLengthName.length should be <= 10
- assert(!fixedLengthName.exists(_.isControl))
+ assert(!fixedLengthName.value.exists(_.isControl))
}
it should "be able to generate proper UUIDs" in {
@@ -82,11 +82,11 @@ class GeneratorsTest extends FlatSpec with Matchers with Assertions {
val generatedPair = nextPair(nextId[Int](), nextName[Int]())
- generatedPair._1 should be > 0L
+ generatedPair._1.length should be > 0
generatedPair._2.length should be > 0
nextPair(nextId[Int](), nextName[Int]()) should not be
- nextPair(nextId[Int](), nextName[Int]())
+ nextPair(nextId[Int](), nextName[Int]())
}
it should "be able to generate a triad of two generated values" in {
@@ -98,12 +98,12 @@ class GeneratorsTest extends FlatSpec with Matchers with Assertions {
val generatedTriad = nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal())
- generatedTriad._1 should be > 0L
+ generatedTriad._1.length should be > 0
generatedTriad._2.length should be > 0
generatedTriad._3 should be >= BigDecimal(0.00)
nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) should not be
- nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal())
+ nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal())
}
it should "be able to generate a time value" in {
diff --git a/src/test/scala/com/drivergrp/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala
index 125e97c..eb8d5d8 100644
--- a/src/test/scala/com/drivergrp/core/JsonTest.scala
+++ b/src/test/scala/xyz/driver/core/JsonTest.scala
@@ -1,20 +1,22 @@
-package com.drivergrp.core
+package xyz.driver.core
-import com.drivergrp.core.json.{EnumJsonFormat, ValueClassFormat}
-import com.drivergrp.core.revision.Revision
-import com.drivergrp.core.time.provider.SystemTimeProvider
import org.scalatest.{FlatSpec, Matchers}
+import xyz.driver.core.json.{EnumJsonFormat, GadtJsonFormat, ValueClassFormat}
+import xyz.driver.core.revision.Revision
+import xyz.driver.core.time.provider.SystemTimeProvider
+import spray.json._
+import xyz.driver.core.TestTypes.CustomGADT
class JsonTest extends FlatSpec with Matchers {
"Json format for Id" should "read and write correct JSON" in {
- val referenceId = Id[String](1312L)
+ val referenceId = Id[String]("1312-34A")
- val writtenJson = com.drivergrp.core.json.idFormat.write(referenceId)
- writtenJson.prettyPrint should be("1312")
+ val writtenJson = json.idFormat.write(referenceId)
+ writtenJson.prettyPrint should be("\"1312-34A\"")
- val parsedId = com.drivergrp.core.json.idFormat.read(writtenJson)
+ val parsedId = json.idFormat.read(writtenJson)
parsedId should be(referenceId)
}
@@ -22,10 +24,10 @@ class JsonTest extends FlatSpec with Matchers {
val referenceName = Name[String]("Homer")
- val writtenJson = com.drivergrp.core.json.nameFormat.write(referenceName)
+ val writtenJson = json.nameFormat.write(referenceName)
writtenJson.prettyPrint should be("\"Homer\"")
- val parsedName = com.drivergrp.core.json.nameFormat.read(writtenJson)
+ val parsedName = json.nameFormat.read(writtenJson)
parsedName should be(referenceName)
}
@@ -33,10 +35,10 @@ class JsonTest extends FlatSpec with Matchers {
val referenceTime = new SystemTimeProvider().currentTime()
- val writtenJson = com.drivergrp.core.json.timeFormat.write(referenceTime)
+ val writtenJson = json.timeFormat.write(referenceTime)
writtenJson.prettyPrint should be("{\n \"timestamp\": " + referenceTime.millis + "\n}")
- val parsedTime = com.drivergrp.core.json.timeFormat.read(writtenJson)
+ val parsedTime = json.timeFormat.read(writtenJson)
parsedTime should be(referenceTime)
}
@@ -44,10 +46,10 @@ class JsonTest extends FlatSpec with Matchers {
val referenceRevision = Revision[String]("037e2ec0-8901-44ac-8e53-6d39f6479db4")
- val writtenJson = com.drivergrp.core.json.revisionFormat.write(referenceRevision)
+ val writtenJson = json.revisionFormat.write(referenceRevision)
writtenJson.prettyPrint should be("\"" + referenceRevision.id + "\"")
- val parsedRevision = com.drivergrp.core.json.revisionFormat.read(writtenJson)
+ val parsedRevision = json.revisionFormat.read(writtenJson)
parsedRevision should be(referenceRevision)
}
@@ -98,4 +100,38 @@ class JsonTest extends FlatSpec with Matchers {
parsedValue1 should be(referenceValue1)
parsedValue2 should be(referenceValue2)
}
+
+ "Json format for classes GADT" should "read and write correct JSON" in {
+
+ import CustomGADT._
+ import DefaultJsonProtocol._
+ implicit val case1Format = jsonFormat1(GadtCase1)
+ implicit val case2Format = jsonFormat1(GadtCase2)
+ implicit val case3Format = jsonFormat1(GadtCase3)
+
+ val format = GadtJsonFormat.create[CustomGADT]("gadtTypeField") {
+ case t1: CustomGADT.GadtCase1 => "case1"
+ case t2: CustomGADT.GadtCase2 => "case2"
+ case t3: CustomGADT.GadtCase3 => "case3"
+ } {
+ case "case1" => case1Format
+ case "case2" => case2Format
+ case "case3" => case3Format
+ }
+
+ val referenceValue1 = CustomGADT.GadtCase1("4")
+ val referenceValue2 = CustomGADT.GadtCase2("Hi!")
+
+ val writtenJson1 = format.write(referenceValue1)
+ writtenJson1 should be("{\n \"field\": \"4\",\n\"gadtTypeField\": \"case1\"\n}".parseJson)
+
+ val writtenJson2 = format.write(referenceValue2)
+ writtenJson2 should be("{\"field\":\"Hi!\",\"gadtTypeField\":\"case2\"}".parseJson)
+
+ val parsedValue1 = format.read(writtenJson1)
+ val parsedValue2 = format.read(writtenJson2)
+
+ parsedValue1 should be(referenceValue1)
+ parsedValue2 should be(referenceValue2)
+ }
}
diff --git a/src/test/scala/com/drivergrp/core/MessagesTest.scala b/src/test/scala/xyz/driver/core/MessagesTest.scala
index 21fe30a..dc44ee1 100644
--- a/src/test/scala/com/drivergrp/core/MessagesTest.scala
+++ b/src/test/scala/xyz/driver/core/MessagesTest.scala
@@ -1,13 +1,13 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.util.Locale
-import com.drivergrp.core.logging.Logger
-import com.drivergrp.core.messages.Messages
import com.typesafe.config.{ConfigException, ConfigFactory}
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FlatSpec, Matchers}
+import xyz.driver.core.logging.Logger
+import xyz.driver.core.messages.Messages
import scala.collection.JavaConversions._
@@ -33,11 +33,11 @@ class MessagesTest extends FlatSpec with Matchers with MockitoSugar {
val log = mock[Logger]
val messagesConfig = ConfigFactory.parseMap(
- englishLocaleMessages ++ Map(
- "zh.hello" -> "你好,世界!",
- "zh.greeting" -> "你好,{0}!",
- "zh.greetingFullName" -> "你好,{0} {1} {2}!"
- ))
+ englishLocaleMessages ++ Map(
+ "zh.hello" -> "你好,世界!",
+ "zh.greeting" -> "你好,{0}!",
+ "zh.greetingFullName" -> "你好,{0} {1} {2}!"
+ ))
val englishMessages = Messages.messages(messagesConfig, log, Locale.US)
val englishMessagesToo = Messages.messages(messagesConfig, log, Locale.ENGLISH)
@@ -50,7 +50,7 @@ class MessagesTest extends FlatSpec with Matchers with MockitoSugar {
englishMessagesToo("hello") should be(englishMessages("hello"))
englishMessagesToo("greeting", "Homer") should be(englishMessages("greeting", "Homer"))
englishMessagesToo("greetingFullName", "Homer", "J", "Simpson") should be(
- englishMessages("greetingFullName", "Homer", "J", "Simpson"))
+ englishMessages("greetingFullName", "Homer", "J", "Simpson"))
chineseMessages("hello") should be("你好,世界!")
chineseMessages("greeting", "Homer") should be("你好,Homer!")
@@ -63,7 +63,7 @@ class MessagesTest extends FlatSpec with Matchers with MockitoSugar {
val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages)
an[ConfigException.Missing] should be thrownBy
- Messages.messages(messagesConfig, log, Locale.GERMAN)
+ Messages.messages(messagesConfig, log, Locale.GERMAN)
}
it should "log a problem, when there is no message for key" in {
diff --git a/src/test/scala/com/drivergrp/core/StatsTest.scala b/src/test/scala/xyz/driver/core/StatsTest.scala
index c4f449b..27ea1bd 100644
--- a/src/test/scala/com/drivergrp/core/StatsTest.scala
+++ b/src/test/scala/xyz/driver/core/StatsTest.scala
@@ -1,11 +1,11 @@
-package com.drivergrp.core
+package xyz.driver.core
-import com.drivergrp.core.logging.Logger
-import com.drivergrp.core.stats.LogStats
-import com.drivergrp.core.time.{Time, TimeRange}
+import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FlatSpec, Matchers}
-import org.mockito.Mockito._
+import xyz.driver.core.logging.Logger
+import xyz.driver.core.stats.LogStats
+import xyz.driver.core.time.{Time, TimeRange}
class StatsTest extends FlatSpec with Matchers with MockitoSugar {
diff --git a/src/test/scala/xyz/driver/core/TestTypes.scala b/src/test/scala/xyz/driver/core/TestTypes.scala
new file mode 100644
index 0000000..bb25deb
--- /dev/null
+++ b/src/test/scala/xyz/driver/core/TestTypes.scala
@@ -0,0 +1,14 @@
+package xyz.driver.core
+
+object TestTypes {
+
+ sealed trait CustomGADT {
+ val field: String
+ }
+
+ object CustomGADT {
+ final case class GadtCase1(field: String) extends CustomGADT
+ final case class GadtCase2(field: String) extends CustomGADT
+ final case class GadtCase3(field: String) extends CustomGADT
+ }
+}
diff --git a/src/test/scala/com/drivergrp/core/TimeTest.scala b/src/test/scala/xyz/driver/core/TimeTest.scala
index b928413..76ef42c 100644
--- a/src/test/scala/com/drivergrp/core/TimeTest.scala
+++ b/src/test/scala/xyz/driver/core/TimeTest.scala
@@ -1,13 +1,13 @@
-package com.drivergrp.core
+package xyz.driver.core
import java.util.TimeZone
-import com.drivergrp.core.time.{Time, _}
-import org.scalacheck.{Arbitrary, Gen}
-import org.scalatest.{FlatSpec, Matchers}
-import org.scalatest.prop.Checkers
import org.scalacheck.Arbitrary._
import org.scalacheck.Prop.BooleanOperators
+import org.scalacheck.{Arbitrary, Gen}
+import org.scalatest.prop.Checkers
+import org.scalatest.{FlatSpec, Matchers}
+import xyz.driver.core.time.{Time, _}
import scala.concurrent.duration._
@@ -56,7 +56,7 @@ class TimeTest extends FlatSpec with Matchers with Checkers {
it should "have ordering defined correctly" in {
Seq(Time(321L), Time(123L), Time(231L)).sorted should
- contain theSameElementsInOrderAs Seq(Time(123L), Time(231L), Time(321L))
+ contain theSameElementsInOrderAs Seq(Time(123L), Time(231L), Time(321L))
check { times: List[Time] =>
times.sorted.sliding(2).filter(_.size == 2).forall {