aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/core/rest/PatchSupport.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/xyz/driver/core/rest/PatchSupport.scala')
-rw-r--r--src/main/scala/xyz/driver/core/rest/PatchSupport.scala107
1 files changed, 0 insertions, 107 deletions
diff --git a/src/main/scala/xyz/driver/core/rest/PatchSupport.scala b/src/main/scala/xyz/driver/core/rest/PatchSupport.scala
deleted file mode 100644
index d7c77c8..0000000
--- a/src/main/scala/xyz/driver/core/rest/PatchSupport.scala
+++ /dev/null
@@ -1,107 +0,0 @@
-package xyz.driver.core.rest
-
-import akka.http.javadsl.server.Rejections
-import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
-import akka.http.scaladsl.server._
-import spray.json._
-import xyz.driver.core.Id
-
-import scala.concurrent.Future
-import scala.util.{Failure, Success, Try}
-import scalaz.syntax.equal._
-import scalaz.Scalaz.stringInstance
-
-trait PatchSupport extends Directives with SprayJsonSupport {
- protected val MergePatchPlusJson: String = "merge-patch+json"
-
- trait PatchRetrievable[T] {
- def apply(id: Id[T])(implicit ctx: ServiceRequestContext): Future[Option[T]]
- }
-
- object PatchRetrievable {
- def apply[T](retriever: (Id[T] => (ServiceRequestContext => Future[Option[T]]))): PatchRetrievable[T] =
- new PatchRetrievable[T] {
- override def apply(id: Id[T])(implicit ctx: ServiceRequestContext): Future[Option[T]] = retriever(id)(ctx)
- }
- }
-
- protected def mergeObjects(oldObj: JsObject, newObj: JsObject, maxLevels: Option[Int] = None): JsObject = {
- JsObject(oldObj.fields.map({
- case (key, oldValue) =>
- val newValue = newObj.fields.get(key).fold(oldValue)(mergeJsValues(oldValue, _, maxLevels.map(_ - 1)))
- key -> newValue
- })(collection.breakOut): _*)
- }
-
- protected def mergeJsValues(oldValue: JsValue, newValue: JsValue, maxLevels: Option[Int] = None): JsValue = {
- def mergeError(typ: String): Nothing =
- deserializationError(s"Expected $typ value, got $newValue")
-
- if (maxLevels.exists(_ < 0)) oldValue
- else {
- (oldValue, newValue) match {
- case (_: JsString, newString @ (JsString(_) | JsNull)) => newString
- case (_: JsString, _) => mergeError("string")
- case (_: JsNumber, newNumber @ (JsNumber(_) | JsNull)) => newNumber
- case (_: JsNumber, _) => mergeError("number")
- case (_: JsBoolean, newBool @ (JsBoolean(_) | JsNull)) => newBool
- case (_: JsBoolean, _) => mergeError("boolean")
- case (_: JsArray, newArr @ (JsArray(_) | JsNull)) => newArr
- case (_: JsArray, _) => mergeError("array")
- case (oldObj: JsObject, newObj: JsObject) => mergeObjects(oldObj, newObj)
- case (_: JsObject, JsNull) => JsNull
- case (_: JsObject, _) => mergeError("object")
- case (JsNull, _) => newValue
- }
- }
- }
-
- def rejectNonMergePatchContentType: Directive0 = Directive { inner => requestCtx =>
- val contentType = requestCtx.request.header[akka.http.scaladsl.model.headers.`Content-Type`]
- val isCorrectContentType =
- contentType.map(_.contentType.mediaType).exists(mt => mt.isApplication && mt.subType === MergePatchPlusJson)
- if (!isCorrectContentType) {
- reject(
- Rejections.malformedRequestContent(
- s"Request Content-Type must be application/$MergePatchPlusJson for PATCH requests",
- new RuntimeException))(requestCtx)
- } else inner(())(requestCtx)
- }
-
- def as[T](
- implicit patchable: PatchRetrievable[T],
- jsonFormat: RootJsonFormat[T]): (PatchRetrievable[T], RootJsonFormat[T]) =
- (patchable, jsonFormat)
-
- def patch[T](patchable: (PatchRetrievable[T], RootJsonFormat[T]), id: Id[T]): Directive1[T] = Directive {
- inner => requestCtx =>
- import requestCtx.executionContext
- val retriever = patchable._1
- implicit val jsonFormat: RootJsonFormat[T] = patchable._2
- Directives.patch {
- rejectNonMergePatchContentType {
- entity(as[JsValue]) { newValue =>
- serviceContext { implicit ctx =>
- onSuccess(retriever(id).map(_.map(_.toJson))) {
- case Some(oldValue) =>
- val mergedObj = mergeJsValues(oldValue, newValue)
- Try(mergedObj.convertTo[T])
- .transform[Route](
- mergedT => scala.util.Success(inner(Tuple1(mergedT))), {
- case jsonException: DeserializationException =>
- Success(reject(Rejections.malformedRequestContent(jsonException.getMessage, jsonException)))
- case t => Failure(t)
- }
- )
- .get // intentionally re-throw all other errors
- case None =>
- reject()
- }
- }
- }
- }
- }(requestCtx)
- }
-}
-
-object PatchSupport extends PatchSupport