aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/pdsuicommon
diff options
context:
space:
mode:
authorvlad <vlad@driver.xyz>2017-07-20 10:37:08 -0700
committervlad <vlad@driver.xyz>2017-07-20 10:37:08 -0700
commit5279d01cedb35a759347f194c0e8adb21d19e88e (patch)
treee340bb2eaba41d917bbca8c5e42b0b76bd164e37 /src/main/scala/xyz/driver/pdsuicommon
parentd9c3283c307105e03253c621b9b25a6308ac3b94 (diff)
parent7f7bd651122754a3df47894b64ddb0456561bbe7 (diff)
downloadrest-query-5279d01cedb35a759347f194c0e8adb21d19e88e.tar.gz
rest-query-5279d01cedb35a759347f194c0e8adb21d19e88e.tar.bz2
rest-query-5279d01cedb35a759347f194c0e8adb21d19e88e.zip
Merge remote-tracking branch 'origin/master'
# Conflicts: # build.sbt # src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala # src/main/scala/xyz/driver/pdsuicommon/domain/User.scala # src/main/scala/xyz/driver/pdsuidomain/formats/json/user/ApiPartialUser.scala
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon')
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala6
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala26
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala31
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala6
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala4
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala8
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/domain/User.scala12
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala11
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala17
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala83
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala2
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala34
16 files changed, 198 insertions, 52 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala
index a21b011..42f7435 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/auth/AuthenticatedRequestContext.scala
@@ -4,7 +4,7 @@ import xyz.driver.entities.users.UserInfo
import xyz.driver.pdsuicommon.logging._
import xyz.driver.pdsuicommon.domain.User
-class AuthenticatedRequestContext(val driverUser: UserInfo, override val requestId: RequestId, val authToken: String = "")
+class AuthenticatedRequestContext(val driverUser: UserInfo, override val requestId: RequestId, val authToken: String)
extends AnonymousRequestContext(requestId) {
val executor: User = new User(driverUser)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala
index 48c81c2..3bf9192 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/BridgeUploadQueueRepositoryAdapter.scala
@@ -77,8 +77,8 @@ object BridgeUploadQueueRepositoryAdapter {
sealed trait OnAttempt
object OnAttempt {
- case object Complete extends OnAttempt
- case class Continue(interval: Duration) extends OnAttempt
+ case object Complete extends OnAttempt
+ final case class Continue(interval: Duration) extends OnAttempt
implicit def toPhiString(x: OnAttempt): PhiString = Unsafe(x.toString)
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala
index dd84beb..6659088 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/Cron.scala
@@ -45,9 +45,9 @@ class Cron(settings: Cron.Settings) extends Closeable with StrictLogging {
* Checks unused jobs
*/
def verify(): Unit = {
- import scala.collection.JavaConversions.asScalaSet
+ import scala.collection.JavaConverters._
- val unusedJobs = settings.intervals.keySet -- jobs.toSet
+ val unusedJobs = settings.intervals.keySet -- jobs.asScala.toSet
unusedJobs.foreach { job =>
logger.warn(s"The job '$job' is listed, but not registered or ignored")
}
@@ -60,7 +60,7 @@ class Cron(settings: Cron.Settings) extends Closeable with StrictLogging {
object Cron {
- case class Settings(disable: String, intervals: Map[String, FiniteDuration])
+ final case class Settings(disable: String, intervals: Map[String, FiniteDuration])
private class SingletonTask(taskName: String, job: () => Future[Unit])(implicit ec: ExecutionContext)
extends TimerTask with StrictLogging {
diff --git a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
index 0bc8220..2f7fe6c 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/concurrent/SafeBridgeUploadQueue.scala
@@ -11,7 +11,7 @@ object SafeBridgeUploadQueue {
trait Tag extends Product with Serializable
- case class SafeTask[T <: Tag](tag: T, private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item)
+ final case class SafeTask[T <: Tag](tag: T, private[SafeBridgeUploadQueue] val queueItem: BridgeUploadQueue.Item)
object SafeTask {
implicit def toPhiString[T <: Tag](x: SafeTask[T]): PhiString = {
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
index f804e87..c547bf4 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/MySqlContext.scala
@@ -18,20 +18,20 @@ import scala.util.{Failure, Success, Try}
object MySqlContext extends PhiLogging {
- case class DbCredentials(user: String,
- password: String,
- host: String,
- port: Int,
- dbName: String,
- dbCreateFlag: Boolean,
- dbContext: String,
- connectionParams: String,
- url: String)
+ final case class DbCredentials(user: String,
+ password: String,
+ host: String,
+ port: Int,
+ dbName: String,
+ dbCreateFlag: Boolean,
+ dbContext: String,
+ connectionParams: String,
+ url: String)
- case class Settings(credentials: DbCredentials,
- connection: Config,
- connectionAttemptsOnStartup: Int,
- threadPoolSize: Int)
+ final case class Settings(credentials: DbCredentials,
+ connection: Config,
+ connectionAttemptsOnStartup: Int,
+ threadPoolSize: Int)
def apply(settings: Settings): MySqlContext = {
// Prevent leaking credentials to a log
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala b/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala
index e72b5c2..92689dd 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/Pagination.scala
@@ -5,7 +5,7 @@ import xyz.driver.pdsuicommon.logging._
/**
* @param pageNumber Starts with 1
*/
-case class Pagination(pageSize: Int, pageNumber: Int)
+final case class Pagination(pageSize: Int, pageNumber: Int)
object Pagination {
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
index f941627..aa32166 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala
@@ -23,15 +23,15 @@ object QueryBuilder {
*/
type Binder = PreparedStatement => PreparedStatement
- case class TableData(tableName: String,
- lastUpdateFieldName: Option[String] = None,
- nullableFields: Set[String] = Set.empty)
+ final case class TableData(tableName: String,
+ lastUpdateFieldName: Option[String] = None,
+ nullableFields: Set[String] = Set.empty)
val AllFields = Set("*")
}
-case class TableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String)
+final case class TableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String)
object QueryBuilderParameters {
val AllFields = Set("*")
@@ -57,10 +57,11 @@ sealed trait QueryBuilderParameters {
def toSql(countQuery: Boolean, fields: Set[String], namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = {
val escapedTableName = namingStrategy.table(tableData.tableName)
val fieldsSql: String = if (countQuery) {
- "count(*)" + (tableData.lastUpdateFieldName match {
+ val suffix: String = (tableData.lastUpdateFieldName match {
case Some(lastUpdateField) => s", max($escapedTableName.${namingStrategy.column(lastUpdateField)})"
case None => ""
})
+ "count(*)" + suffix
} else {
if (fields == QueryBuilderParameters.AllFields) {
s"$escapedTableName.*"
@@ -260,11 +261,11 @@ sealed trait QueryBuilderParameters {
}
-case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData,
- links: Map[String, TableLink] = Map.empty,
- filter: SearchFilterExpr = SearchFilterExpr.Empty,
- sorting: Sorting = Sorting.Empty,
- pagination: Option[Pagination] = None)
+final case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData,
+ links: Map[String, TableLink] = Map.empty,
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Sorting = Sorting.Empty,
+ pagination: Option[Pagination] = None)
extends QueryBuilderParameters {
def limitToSql(): String = {
@@ -279,11 +280,11 @@ case class PostgresQueryBuilderParameters(tableData: QueryBuilder.TableData,
/**
* @param links Links to another tables grouped by foreignTableName
*/
-case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData,
- links: Map[String, TableLink] = Map.empty,
- filter: SearchFilterExpr = SearchFilterExpr.Empty,
- sorting: Sorting = Sorting.Empty,
- pagination: Option[Pagination] = None)
+final case class MysqlQueryBuilderParameters(tableData: QueryBuilder.TableData,
+ links: Map[String, TableLink] = Map.empty,
+ filter: SearchFilterExpr = SearchFilterExpr.Empty,
+ sorting: Sorting = Sorting.Empty,
+ pagination: Option[Pagination] = None)
extends QueryBuilderParameters {
def limitToSql(): String =
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
index 4b66f22..0577921 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/SearchFilterExpr.scala
@@ -16,7 +16,7 @@ object SearchFilterExpr {
value = "false"
)
- case class Dimension(tableName: Option[String], name: String) {
+ final case class Dimension(tableName: Option[String], name: String) {
def isForeign: Boolean = tableName.isDefined
}
@@ -33,13 +33,13 @@ object SearchFilterExpr {
}
object Atom {
- case class Binary(dimension: Dimension, op: SearchFilterBinaryOperation, value: AnyRef) extends Atom
+ final case class Binary(dimension: Dimension, op: SearchFilterBinaryOperation, value: AnyRef) extends Atom
object Binary {
def apply(field: String, op: SearchFilterBinaryOperation, value: AnyRef): Binary =
Binary(Dimension(None, field), op, value)
}
- case class NAry(dimension: Dimension, op: SearchFilterNAryOperation, values: Seq[AnyRef]) extends Atom
+ final case class NAry(dimension: Dimension, op: SearchFilterNAryOperation, values: Seq[AnyRef]) extends Atom
object NAry {
def apply(field: String, op: SearchFilterNAryOperation, values: Seq[AnyRef]): NAry =
NAry(Dimension(None, field), op, values)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala b/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala
index b796b83..a2c5a75 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/db/Sorting.scala
@@ -23,11 +23,11 @@ object Sorting {
* @param name Dimension name
* @param order Order
*/
- case class Dimension(tableName: Option[String], name: String, order: SortingOrder) extends Sorting {
+ final case class Dimension(tableName: Option[String], name: String, order: SortingOrder) extends Sorting {
def isForeign: Boolean = tableName.isDefined
}
- case class Sequential(sorting: Seq[Dimension]) extends Sorting {
+ final case class Sequential(sorting: Seq[Dimension]) extends Sorting {
override def toString: String = if (isEmpty(this)) "Empty" else super.toString
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala
index 1bb70f8..e238245 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/domain/Id.scala
@@ -6,9 +6,9 @@ import xyz.driver.pdsuicommon.logging._
sealed trait Id[+T]
-case class CompoundId[Id1 <: Id[_], Id2 <: Id[_]](part1: Id1, part2: Id2) extends Id[(Id1, Id2)]
+final case class CompoundId[Id1 <: Id[_], Id2 <: Id[_]](part1: Id1, part2: Id2) extends Id[(Id1, Id2)]
-case class LongId[+T](id: Long) extends Id[T] {
+final case class LongId[+T](id: Long) extends Id[T] {
override def toString: String = id.toString
def is(longId: Long): Boolean = {
@@ -20,7 +20,7 @@ object LongId {
implicit def toPhiString[T](x: LongId[T]): PhiString = Unsafe(s"LongId(${x.id})")
}
-case class StringId[+T](id: String) extends Id[T] {
+final case class StringId[+T](id: String) extends Id[T] {
override def toString: String = id
def is(stringId: String): Boolean = {
@@ -32,7 +32,7 @@ object StringId {
implicit def toPhiString[T](x: StringId[T]): PhiString = Unsafe(s"StringId(${x.id})")
}
-case class UuidId[+T](id: UUID) extends Id[T] {
+final case class UuidId[+T](id: UUID) extends Id[T] {
override def toString: String = id.toString
}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala
index b400a71..654af1a 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/domain/User.scala
@@ -8,12 +8,12 @@ import xyz.driver.pdsuicommon.logging._
import xyz.driver.pdsuicommon.domain.User.Role
import xyz.driver.pdsuicommon.utils.Utils
-case class User(id: StringId[User],
- email: Email,
- name: String,
- roles: Set[Role],
- latestActivity: Option[LocalDateTime],
- deleted: Option[LocalDateTime]) {
+final case class User(id: StringId[User],
+ email: Email,
+ name: String,
+ roles: Set[Role],
+ latestActivity: Option[LocalDateTime],
+ deleted: Option[LocalDateTime]) {
def this(driverUser: xyz.driver.entities.users.UserInfo) {
this(
diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala b/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala
index 4bf90d1..c761414 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala
@@ -28,3 +28,14 @@ object DomainError {
Unsafe(Utils.getClassSimpleName(x.getClass))
}
}
+
+/** Subclasses of this exception correspond to subclasses of DomainError. They
+ * are used in REST service implementations to fail futures rather than
+ * returning successful futures, completed with corresponding DomainErrors. */
+// scalastyle:off null
+@SuppressWarnings(Array("org.wartremover.warts.Null"))
+class DomainException(message: String, cause: Throwable = null) extends RuntimeException(message, cause)
+class NotFoundException(message: String) extends DomainException(message) // 404
+class AuthenticationException(message: String) extends DomainException(message) // 401
+class AuthorizationException(message: String) extends DomainException(message) // 403
+// scalastyle:on null
diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala
new file mode 100644
index 0000000..5574c01
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorCode.scala
@@ -0,0 +1,17 @@
+package xyz.driver.pdsuicommon.error
+
+import play.api.libs.functional.syntax._
+import play.api.libs.json.{Format, Reads, Writes}
+
+@SuppressWarnings(Array("org.wartremover.warts.Enumeration"))
+object ErrorCode extends Enumeration {
+
+ type ErrorCode = Value
+ val Unspecified = Value(1)
+
+ private val fromJsonReads: Reads[ErrorCode] = Reads.of[Int].map(ErrorCode.apply)
+ private val toJsonWrites: Writes[ErrorCode] = Writes.of[Int].contramap(_.id)
+
+ implicit val jsonFormat: Format[ErrorCode] = Format(fromJsonReads, toJsonWrites)
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala
new file mode 100644
index 0000000..3761cc5
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/error/ErrorsResponse.scala
@@ -0,0 +1,83 @@
+package xyz.driver.pdsuicommon.error
+
+import xyz.driver.pdsuicommon.json.Serialization.seqJsonFormat
+import ErrorCode.{ErrorCode, Unspecified}
+import ErrorsResponse.ResponseError
+import xyz.driver.pdsuicommon.auth.{AnonymousRequestContext, RequestId}
+import xyz.driver.pdsuicommon.utils.Utils
+import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import play.api.mvc.Results
+import xyz.driver.pdsuicommon.validation.JsonValidationErrors
+
+final case class ErrorsResponse(errors: Seq[ResponseError], requestId: RequestId)
+
+object ErrorsResponse {
+
+ /**
+ * @param data Any data that can be associated with particular error.Ex.: error field name
+ * @param message Error message
+ * @param code Unique error code
+ *
+ * @see https://driverinc.atlassian.net/wiki/display/RA/REST+API+Specification#RESTAPISpecification-HTTPStatuscodes
+ */
+ final case class ResponseError(data: Option[String], message: String, code: ErrorCode)
+
+ object ResponseError {
+
+ implicit val responseErrorJsonFormat: Format[ResponseError] = (
+ (JsPath \ "data").formatNullable[String] and
+ (JsPath \ "message").format[String] and
+ (JsPath \ "code").format[ErrorCode]
+ )(ResponseError.apply, unlift(ResponseError.unapply))
+
+ }
+
+ implicit val errorsResponseJsonFormat: Format[ErrorsResponse] = (
+ (JsPath \ "errors").format[Seq[ResponseError]] and
+ (JsPath \ "requestId").format[String]
+ )((errs, req) => ErrorsResponse.apply(errs, RequestId(req)), res => (res.errors, res.requestId.value))
+
+ // deprecated, will be removed in REP-436
+ def fromString(message: String, httpStatus: Results#Status)(
+ implicit context: AnonymousRequestContext): ErrorsResponse = {
+ new ErrorsResponse(
+ errors = Seq(
+ ResponseError(
+ data = None,
+ message = message,
+ code = Unspecified
+ )),
+ requestId = context.requestId
+ )
+ }
+
+ // scalastyle:off null
+ def fromExceptionMessage(e: Throwable, httpStatus: Results#Status = Results.InternalServerError)(
+ implicit context: AnonymousRequestContext): ErrorsResponse = {
+ val message = if (e.getMessage == null || e.getMessage.isEmpty) {
+ Utils.getClassSimpleName(e.getClass)
+ } else {
+ e.getMessage
+ }
+
+ fromString(message, httpStatus)
+ }
+ // scalastyle:on null
+
+ // deprecated, will be removed in REP-436
+ def fromJsonValidationErrors(validationErrors: JsonValidationErrors)(
+ implicit context: AnonymousRequestContext): ErrorsResponse = {
+ val errors = validationErrors.map {
+ case (path, xs) =>
+ ResponseError(
+ data = Some(path.toString()),
+ message = xs.map(_.message).mkString("\n"),
+ code = Unspecified
+ )
+ }
+
+ new ErrorsResponse(errors, context.requestId)
+ }
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala
index 07dfefc..4ff4034 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/json/JsResultOps.scala
@@ -7,7 +7,7 @@ import scala.util.{Failure, Success, Try}
final class JsResultOps[T](val self: JsResult[T]) extends AnyVal {
def toTry: Try[T] = {
- self.fold(
+ self.fold[Try[T]](
errors => Failure(new JsonValidationException(errors)),
Success(_)
)
diff --git a/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala b/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala
new file mode 100644
index 0000000..5158dab
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuicommon/serialization/PlayJsonSupport.scala
@@ -0,0 +1,34 @@
+package xyz.driver.pdsuicommon.serialization
+
+import akka.http.scaladsl.server.{RejectionError, ValidationRejection}
+import akka.http.scaladsl.unmarshalling.Unmarshaller
+import play.api.libs.json.{Reads, Writes}
+import play.api.libs.json.Json
+import akka.http.scaladsl.marshalling.ToEntityMarshaller
+import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
+import akka.http.scaladsl.model.MediaTypes.`application/json`
+
+trait PlayJsonSupport {
+ import akka.http.scaladsl.marshalling.Marshaller
+
+ implicit def playJsonUnmarshaller[A: Reads]: FromEntityUnmarshaller[A] = {
+ val reads = implicitly[Reads[A]]
+ Unmarshaller.stringUnmarshaller
+ .forContentTypes(`application/json`)
+ .map(Json.parse)
+ .map(reads.reads)
+ .map(_.recoverTotal { error =>
+ throw RejectionError(ValidationRejection(s"Error reading JSON response as ${reads}."))
+ })
+ }
+
+ implicit def playJsonMarshaller[A: Writes]: ToEntityMarshaller[A] = {
+ Marshaller
+ .stringMarshaller(`application/json`)
+ .compose(Json.prettyPrint)
+ .compose(implicitly[Writes[A]].writes)
+ }
+
+}
+
+object PlayJsonSupport extends PlayJsonSupport