aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKseniya Tomskikh <ktomskih@datamonsters.co>2018-01-10 12:09:10 +0700
committerKseniya Tomskikh <ktomskih@datamonsters.co>2018-01-10 12:09:10 +0700
commit7d9c3a7a0de129eef06d5b2eac92aafec1229098 (patch)
tree74c839223186e73c017b2e07a5cd43e9754498e5
parent2456ac8336df69734890b325b0b2a5a1182e93e2 (diff)
parent5bbb0bc66db5b49eb0e3f197be17798fb8093f2a (diff)
downloadrest-query-7d9c3a7a0de129eef06d5b2eac92aafec1229098.tar.gz
rest-query-7d9c3a7a0de129eef06d5b2eac92aafec1229098.tar.bz2
rest-query-7d9c3a7a0de129eef06d5b2eac92aafec1229098.zip
Merge branch 'master' into PDSUI-2432
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala14
-rw-r--r--src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala19
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/documentissue.scala12
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/formats/json/trialissue.scala88
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/EligibilityArmService.scala3
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala19
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialIssueService.scala103
-rw-r--r--src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala2
-rw-r--r--src/test/scala/xyz/driver/pdsuidomain/formats/json/TrialIssueFormatSuite.scala1
9 files changed, 203 insertions, 58 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala b/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala
index c761414..becb585 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/error/DomainError.scala
@@ -16,9 +16,6 @@ object DomainError {
// 404 error
trait NotFoundError extends DomainError
- // 401 error
- trait AuthenticationError extends DomainError
-
// 403 error
trait AuthorizationError extends DomainError
@@ -28,14 +25,3 @@ 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/http/Directives.scala b/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala
index 4f53a9d..46b86a6 100644
--- a/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala
+++ b/src/main/scala/xyz/driver/pdsuicommon/http/Directives.scala
@@ -62,10 +62,9 @@ trait Directives {
PathMatchers.JavaUUID.map((id) => UuidId(id))
def failFast[A](reply: A): A = reply match {
- case err: NotFoundError => throw new NotFoundException(err.getMessage)
- case err: AuthenticationError => throw new AuthenticationException(err.getMessage)
- case err: AuthorizationError => throw new AuthorizationException(err.getMessage)
- case err: DomainError => throw new DomainException(err.getMessage)
+ case err: NotFoundError => throw ResourceNotFoundException(err.getMessage)
+ case err: AuthorizationError => throw InvalidActionException(err.getMessage)
+ case err: DomainError => throw InvalidInputException(err.getMessage)
case other => other
}
@@ -73,27 +72,15 @@ trait Directives {
def errorResponse(msg: String, code: Int) =
ErrorsResponse(Seq(ResponseError(None, msg, code)), req)
ExceptionHandler {
- case ex: AuthenticationException =>
- complete(StatusCodes.Unauthorized -> errorResponse(ex.getMessage, 401))
-
case ex: InvalidActionException =>
complete(StatusCodes.Forbidden -> errorResponse(ex.message, 403))
- case ex: AuthorizationException =>
- complete(StatusCodes.Forbidden -> errorResponse(ex.getMessage, 403))
-
case ex: ResourceNotFoundException =>
complete(StatusCodes.NotFound -> errorResponse(ex.message, 404))
- case ex: NotFoundException =>
- complete(StatusCodes.NotFound -> errorResponse(ex.getMessage, 404))
-
case ex: InvalidInputException =>
complete(StatusCodes.BadRequest -> errorResponse(ex.message, 400))
- case ex: DomainException =>
- complete(StatusCodes.BadRequest -> errorResponse(ex.getMessage, 400))
-
case NonFatal(ex) =>
complete(StatusCodes.InternalServerError -> errorResponse(ex.getMessage, 500))
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/documentissue.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/documentissue.scala
index d17a45c..037a3ad 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/documentissue.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/documentissue.scala
@@ -24,14 +24,14 @@ object documentissue {
.map(_.convertTo[Boolean])
.getOrElse(deserializationError(s"DocumentIssue json object does not contain `archiveRequired` field: $json"))
- val startPage = fields.get("startPage").map(_.convertTo[Double])
- val endPage = fields.get("endPage").map(_.convertTo[Double])
+ val startPage = fields.get("startPage").map(_.convertTo[Option[Double]])
+ val endPage = fields.get("endPage").map(_.convertTo[Option[Double]])
orig.copy(
text = text,
archiveRequired = archiveRequired,
- startPage = startPage,
- endPage = endPage
+ startPage = startPage.getOrElse(orig.startPage),
+ endPage = endPage.getOrElse(orig.endPage)
)
case _ => deserializationError(s"Expected Json Object as partial DocumentIssue, but got $json")
@@ -49,8 +49,8 @@ object documentissue {
.getOrElse(deserializationError(s"DocumentIssue json object does not contain `text` field: $json"))
val id = fields.get("id").map(_.convertTo[LongId[DocumentIssue]])
- val startPage = fields.get("startPage").map(_.convertTo[Double])
- val endPage = fields.get("endPage").map(_.convertTo[Double])
+ val startPage = fields.get("startPage").flatMap(_.convertTo[Option[Double]])
+ val endPage = fields.get("endPage").flatMap(_.convertTo[Option[Double]])
DocumentIssue(
id = id.getOrElse(LongId(0)),
diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trialissue.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trialissue.scala
index f9bb0b6..79882c2 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/trialissue.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/trialissue.scala
@@ -2,16 +2,26 @@ package xyz.driver.pdsuidomain.formats.json
import java.time.LocalDateTime
-import spray.json._
+import spray.json.{RootJsonReader, _}
+import xyz.driver.core.{Id, auth}
import xyz.driver.core.auth.User
import xyz.driver.core.json._
import xyz.driver.pdsuicommon.domain.{LongId, StringId}
import xyz.driver.pdsuidomain.entities._
object trialissue {
+
import DefaultJsonProtocol._
import common._
+ private def deserializationErrorFieldMessage(field: String, json: JsValue)(implicit className: String) = {
+ deserializationError(s"$className json object do not contain '$field' field: $json")
+ }
+
+ private def deserializationErrorEntityMessage(json: JsValue)(implicit className: String) = {
+ deserializationError(s"Expected Json Object as $className, but got $json")
+ }
+
def applyUpdateToTrialIssue(json: JsValue, orig: TrialIssue): TrialIssue = {
json.asJsObject.getFields("text", "evidence", "archiveRequired", "meta") match {
case Seq(text, evidence, archiveRequired, meta) =>
@@ -48,15 +58,77 @@ object trialissue {
implicit val trialIssueWriter = new RootJsonWriter[TrialIssue] {
override def write(obj: TrialIssue) = JsObject(
- "id" -> obj.id.toJson,
- "text" -> obj.text.toJson,
- "lastUpdate" -> obj.lastUpdate.toJson,
- "userId" -> obj.userId.toJson,
- "isDraft" -> obj.isDraft.toJson,
- "evidence" -> obj.evidence.toJson,
+ "id" -> obj.id.toJson,
+ "text" -> obj.text.toJson,
+ "lastUpdate" -> obj.lastUpdate.toJson,
+ "userId" -> obj.userId.toJson,
+ "isDraft" -> obj.isDraft.toJson,
+ "evidence" -> obj.evidence.toJson,
"archiveRequired" -> obj.archiveRequired.toJson,
- "meta" -> obj.meta.toJson
+ "meta" -> obj.meta.toJson
)
}
+ def trialIssueReader(trialId: StringId[Trial]): RootJsonReader[TrialIssue] =
+ new RootJsonReader[TrialIssue] {
+ implicit val className: String = "TrialIssue"
+
+ override def read(json: JsValue): TrialIssue = json match {
+ case JsObject(fields) =>
+ val id = fields
+ .get("id")
+ .map(_.convertTo[LongId[TrialIssue]])
+ .getOrElse(deserializationErrorFieldMessage("id", json))
+
+ val text = fields
+ .get("text")
+ .map(_.convertTo[String])
+ .getOrElse(deserializationErrorFieldMessage("text", json))
+
+ val lastUpdate = fields
+ .get("lastUpdate")
+ .map(_.convertTo[LocalDateTime])
+ .getOrElse(deserializationErrorFieldMessage("lastUpdate", json))
+
+ val userId = fields
+ .get("userId")
+ .map(_.convertTo[Id[auth.User]])
+ .getOrElse(deserializationErrorFieldMessage("userId", json))
+
+ val isDraft = fields
+ .get("isDraft")
+ .map(_.convertTo[Boolean])
+ .getOrElse(deserializationErrorFieldMessage("isDraft", json))
+
+ val evidence = fields
+ .get("evidence")
+ .map(_.convertTo[String])
+ .getOrElse(deserializationErrorFieldMessage("evidence", json))
+
+ val archiveRequired = fields
+ .get("archiveRequired")
+ .map(_.convertTo[Boolean])
+ .getOrElse(deserializationErrorFieldMessage("archiveRequired", json))
+
+ val meta = fields
+ .get("meta")
+ .map(_.convertTo[String])
+ .getOrElse(deserializationErrorFieldMessage("meta", json))
+
+ TrialIssue(
+ id = id,
+ userId = userId,
+ trialId = trialId,
+ lastUpdate = lastUpdate,
+ isDraft = isDraft,
+ text = text,
+ evidence = evidence,
+ archiveRequired = archiveRequired,
+ meta = meta
+ )
+
+ case _ => deserializationErrorEntityMessage(json)
+ }
+ }
+
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/EligibilityArmService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/EligibilityArmService.scala
index 300091e..8e67627 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/EligibilityArmService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/EligibilityArmService.scala
@@ -125,6 +125,9 @@ trait EligibilityArmService {
def getByEligibilityId(armId: LongId[EligibilityArm])(
implicit requestContext: AuthorizedServiceRequestContext[AuthUserInfo]): Future[GetByIdReply]
+ def deleteByEligibilityIdSlotArmId(armId: LongId[EligibilityArm], slotArmId: LongId[SlotArm])(
+ implicit requestContext: AuthorizedServiceRequestContext[AuthUserInfo]): Future[DeleteReply]
+
def getBySlotId(armId: LongId[SlotArm],
filter: SearchFilterExpr = SearchFilterExpr.Empty,
sorting: Option[Sorting] = None,
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala
index 527ae73..8ed2651 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala
@@ -4,14 +4,8 @@ import scala.concurrent.{ExecutionContext, Future}
import akka.http.scaladsl.model.{HttpResponse, ResponseEntity, StatusCodes, Uri}
import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller}
import akka.stream.Materializer
-import xyz.driver.pdsuicommon.db.{
- Pagination,
- SearchFilterBinaryOperation,
- SearchFilterExpr,
- SearchFilterNAryOperation,
- Sorting,
- SortingOrder
-}
+import xyz.driver.core.rest.errors.{InvalidActionException, InvalidInputException, ResourceNotFoundException}
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterBinaryOperation, SearchFilterExpr, SearchFilterNAryOperation, Sorting, SortingOrder}
import xyz.driver.pdsuicommon.error._
trait RestHelper {
@@ -93,7 +87,7 @@ trait RestHelper {
.to[ErrorsResponse]
.transform(
response => response.errors.map(_.message).mkString(", "),
- ex => new DomainException("Response has invalid format", ex)
+ ex => InvalidInputException(s"Response has invalid format: ${ex.getMessage}")
)
}
@@ -102,11 +96,10 @@ trait RestHelper {
} else {
extractErrorMessage(response).flatMap { message =>
Future.failed(response.status match {
- case StatusCodes.Unauthorized => new AuthenticationException(message)
- case StatusCodes.Forbidden => new AuthorizationException(message)
- case StatusCodes.NotFound => new NotFoundException(message)
+ case StatusCodes.Forbidden => InvalidActionException(message)
+ case StatusCodes.NotFound => ResourceNotFoundException(message)
case other =>
- new DomainException(s"Unhandled domain error for HTTP status ${other.value}. $message")
+ InvalidInputException(s"Unhandled domain error for HTTP status ${other.value}. $message")
})
}
}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialIssueService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialIssueService.scala
new file mode 100644
index 0000000..b14d35b
--- /dev/null
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialIssueService.scala
@@ -0,0 +1,103 @@
+package xyz.driver.pdsuidomain.services.rest
+
+import akka.http.scaladsl.marshalling.Marshal
+import akka.http.scaladsl.model._
+import akka.stream.Materializer
+import spray.json.RootJsonReader
+import xyz.driver.core.rest.{AuthorizedServiceRequestContext, ServiceTransport}
+import xyz.driver.entities.users
+import xyz.driver.pdsuicommon.db.{Pagination, SearchFilterExpr, Sorting}
+import xyz.driver.pdsuicommon.domain.{LongId, StringId}
+import xyz.driver.pdsuidomain.ListResponse
+import xyz.driver.pdsuidomain.entities.{Trial, TrialIssue}
+import xyz.driver.pdsuidomain.services.TrialIssueService
+
+import scala.concurrent.ExecutionContext
+
+class RestTrialIssueService(transport: ServiceTransport, baseUri: Uri)
+ (implicit
+ protected val materializer: Materializer,
+ protected val exec: ExecutionContext)
+ extends TrialIssueService with RestHelper{
+
+ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
+ import xyz.driver.pdsuidomain.formats.json.listresponse._
+ import xyz.driver.pdsuidomain.formats.json.trialissue._
+ import xyz.driver.pdsuidomain.services.TrialIssueService._
+
+ override def create(draft: TrialIssue)
+ (implicit requestContext: AuthorizedServiceRequestContext[users.AuthUserInfo]) = {
+ val trialId = draft.trialId
+
+ implicit val jsonReader: RootJsonReader[TrialIssue] = trialIssueReader(trialId)
+
+ for {
+ entity <- Marshal(draft).to[RequestEntity]
+ request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, s"/v1/trial/$trialId/issue")).withEntity(entity)
+ response <- transport.sendRequestGetResponse(requestContext)(request)
+ reply <- apiResponse[TrialIssue](response)
+ } yield {
+ CreateReply.Created(reply)
+ }
+ }
+
+ override def getListByTrialId(trialId: StringId[Trial],
+ filter: SearchFilterExpr,
+ sorting: Option[Sorting],
+ pagination: Option[Pagination])
+ (implicit requestContext: AuthorizedServiceRequestContext[users.AuthUserInfo]) = {
+ implicit val jsonReader: RootJsonReader[TrialIssue] = trialIssueReader(trialId)
+
+ val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/$trialId/issue",
+ filterQuery(filter) ++ sortingQuery(sorting) ++ paginationQuery(pagination))
+ )
+ for {
+ response <- transport.sendRequestGetResponse(requestContext)(request)
+ reply <- apiResponse[ListResponse[TrialIssue]](response)
+ } yield {
+ GetListByTrialIdReply.EntityList(reply.items, reply.meta.itemsCount, reply.meta.lastUpdate)
+ }
+ }
+
+ override def getById(trialId: StringId[Trial], id: LongId[TrialIssue])
+ (implicit requestContext: AuthorizedServiceRequestContext[users.AuthUserInfo]) = {
+ implicit val jsonReader: RootJsonReader[TrialIssue] = trialIssueReader(trialId)
+
+ val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/$trialId/issue/$id"))
+ for {
+ response <- transport.sendRequestGetResponse(requestContext)(request)
+ reply <- apiResponse[TrialIssue](response)
+ } yield {
+ GetByIdReply.Entity(reply)
+ }
+ }
+
+ override def update(orig: TrialIssue, draft: TrialIssue)
+ (implicit requestContext: AuthorizedServiceRequestContext[users.AuthUserInfo]) = {
+ val trialId = draft.trialId
+ val id = orig.id.id
+
+ implicit val jsonReader: RootJsonReader[TrialIssue] = trialIssueReader(trialId)
+
+ for {
+ entity <- Marshal(draft).to[RequestEntity]
+ request = HttpRequest(HttpMethods.PATCH, endpointUri(baseUri, s"/v1/trial/$trialId/issue/$id")).withEntity(entity)
+ response <- transport.sendRequestGetResponse(requestContext)(request)
+ reply <- apiResponse[TrialIssue](response)
+ } yield {
+ UpdateReply.Updated(reply)
+ }
+ }
+
+ override def delete(trialId: StringId[Trial], id: LongId[TrialIssue])
+ (implicit requestContext: AuthorizedServiceRequestContext[users.AuthUserInfo]) = {
+ val request = HttpRequest(HttpMethods.DELETE, endpointUri(baseUri, s"/v1/trial/$trialId/issue/$id"))
+ for {
+ response <- transport.sendRequestGetResponse(requestContext)(request)
+ _ <- apiResponse[HttpEntity](response)
+ } yield {
+ DeleteReply.Deleted
+ }
+ }
+
+}
diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala
index 4654c2e..6650d51 100644
--- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala
+++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala
@@ -110,7 +110,7 @@ class RestTrialService(transport: ServiceTransport, baseUri: Uri)(implicit prote
}
val id = origTrial.id.id
- val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/$id/$action", query))
+ val request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, s"/v1/trial/$id/$action", query))
for {
response <- transport.sendRequestGetResponse(requestContext)(request)
reply <- apiResponse[Trial](response)
diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/TrialIssueFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/TrialIssueFormatSuite.scala
index a3b5931..a13e097 100644
--- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/TrialIssueFormatSuite.scala
+++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/TrialIssueFormatSuite.scala
@@ -26,6 +26,7 @@ class TrialIssueFormatSuite extends FlatSpec with Matchers {
writtenJson should be("""{"id":10,"userId":"userId-001","lastUpdate":"2017-08-10T18:00Z","isDraft":false,
"text":"message text","evidence":"evidence","archiveRequired":false,"meta":"{}"}""".parseJson)
+ trialIssueReader(StringId("NCT000001")).read(writtenJson) shouldBe trialIssue
val createTrialIssueJson = """{"text":"message text","evidence":"evidence","meta":"{}"}""".parseJson
val expectedCreatedTrialIssue = trialIssue.copy(id = LongId(0), lastUpdate = LocalDateTime.MIN, isDraft = true)