From 68537891ca8f1b06301bbe7b996e5eb4ed680e39 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Wed, 18 Oct 2017 14:15:45 +0700 Subject: Added REST API methods to HypothesisService --- .../pdsuidomain/services/HypothesisService.scala | 39 ++++++++++++++++++++++ .../services/rest/RestHypothesisService.scala | 35 +++++++++++++++---- 2 files changed, 68 insertions(+), 6 deletions(-) (limited to 'src/main/scala/xyz/driver/pdsuidomain/services') diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala index 52cd6c8..9dc8d33 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala @@ -2,12 +2,21 @@ package xyz.driver.pdsuidomain.services import xyz.driver.pdsuicommon.auth.AuthenticatedRequestContext import xyz.driver.pdsuicommon.db.Sorting +import xyz.driver.pdsuicommon.domain.UuidId import xyz.driver.pdsuicommon.error.DomainError import xyz.driver.pdsuidomain.entities.Hypothesis import scala.concurrent.Future object HypothesisService { + trait DefaultNotFoundError { + def userMessage: String = "Intervention not found" + } + + trait DefaultAccessDeniedError { + def userMessage: String = "Access denied" + } + sealed trait GetListReply object GetListReply { final case class EntityList(xs: Seq[Hypothesis], totalFound: Int) extends GetListReply @@ -16,6 +25,32 @@ object HypothesisService { def userMessage: String = "Access denied" } } + + sealed trait CreateReply + object CreateReply { + final case class Created(x: Hypothesis) extends CreateReply + + type Error = CreateReply with DomainError + + case object AuthorizationError + extends CreateReply with DefaultAccessDeniedError with DomainError.AuthorizationError + + final case class CommonError(userMessage: String) extends CreateReply with DomainError + } + + sealed trait DeleteReply + object DeleteReply { + case object Deleted extends DeleteReply + + type Error = DeleteReply with DomainError + + case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError + + case object AuthorizationError + extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError + + final case class CommonError(userMessage: String) extends DeleteReply with DomainError + } } trait HypothesisService { @@ -24,4 +59,8 @@ trait HypothesisService { def getAll(sorting: Option[Sorting] = None)( implicit requestContext: AuthenticatedRequestContext): Future[GetListReply] + + def create(draftHypothesis: Hypothesis)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] + + def delete(id: UuidId[Hypothesis])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala index 1b8c943..9cd0847 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala @@ -1,14 +1,17 @@ package xyz.driver.pdsuidomain.services.rest import scala.concurrent.{ExecutionContext, Future} - +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ +import akka.http.scaladsl.marshalling.Marshal import akka.http.scaladsl.model._ import akka.stream.Materializer import xyz.driver.core.rest._ import xyz.driver.pdsuicommon.auth._ import xyz.driver.pdsuicommon.db._ -import xyz.driver.pdsuidomain.formats.json.ListResponse -import xyz.driver.pdsuidomain.formats.json.hypothesis.ApiHypothesis +import xyz.driver.pdsuicommon.domain.UuidId +import xyz.driver.pdsuidomain.entities.Hypothesis +import xyz.driver.pdsuidomain.formats.json.sprayformats.ListResponse +import xyz.driver.pdsuidomain.formats.json.sprayformats.hypothesis._ import xyz.driver.pdsuidomain.services.HypothesisService class RestHypothesisService(transport: ServiceTransport, baseUri: Uri)( @@ -16,7 +19,6 @@ class RestHypothesisService(transport: ServiceTransport, baseUri: Uri)( protected val exec: ExecutionContext) extends HypothesisService with RestHelper { - import xyz.driver.pdsuicommon.serialization.PlayJsonSupport._ import xyz.driver.pdsuidomain.services.HypothesisService._ def getAll(sorting: Option[Sorting] = None)( @@ -24,9 +26,30 @@ class RestHypothesisService(transport: ServiceTransport, baseUri: Uri)( val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, "/v1/hypothesis", sortingQuery(sorting))) for { response <- transport.sendRequestGetResponse(requestContext)(request) - reply <- apiResponse[ListResponse[ApiHypothesis]](response) + reply <- apiResponse[ListResponse[Hypothesis]](response) + } yield { + GetListReply.EntityList(reply.items, reply.meta.itemsCount) + } + } + + def create(draftHypothesis: Hypothesis)(implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] = { + for { + entity <- Marshal(draftHypothesis).to[RequestEntity] + request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, "/v1/hypothesis")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[Hypothesis](response) + } yield { + CreateReply.Created(reply) + } + } + + def delete(id: UuidId[Hypothesis])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] = { + val request = HttpRequest(HttpMethods.DELETE, endpointUri(baseUri, s"/v1/hypothesis/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + _ <- apiResponse[HttpEntity](response) } yield { - GetListReply.EntityList(reply.items.map(_.toDomain), reply.meta.itemsCount) + DeleteReply.Deleted } } -- cgit v1.2.3 From 997407855c2293264dd160e6841f342aeaccf02a Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Fri, 20 Oct 2017 10:22:28 +0700 Subject: Fixed typo in HypothesisService --- src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main/scala/xyz/driver/pdsuidomain/services') diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala index 9dc8d33..572edb6 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/HypothesisService.scala @@ -10,7 +10,7 @@ import scala.concurrent.Future object HypothesisService { trait DefaultNotFoundError { - def userMessage: String = "Intervention not found" + def userMessage: String = "Hypothesis not found" } trait DefaultAccessDeniedError { -- cgit v1.2.3 From e4ef78966ccb9af789c76339812b860395f0a9de Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Fri, 20 Oct 2017 15:02:19 +0700 Subject: Resolved breaking changes --- .../xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main/scala/xyz/driver/pdsuidomain/services') diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala index 9cd0847..9cef4c8 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHypothesisService.scala @@ -10,7 +10,8 @@ import xyz.driver.pdsuicommon.auth._ import xyz.driver.pdsuicommon.db._ import xyz.driver.pdsuicommon.domain.UuidId import xyz.driver.pdsuidomain.entities.Hypothesis -import xyz.driver.pdsuidomain.formats.json.sprayformats.ListResponse +import xyz.driver.pdsuidomain.ListResponse +import xyz.driver.pdsuidomain.formats.json.sprayformats.listresponse._ import xyz.driver.pdsuidomain.formats.json.sprayformats.hypothesis._ import xyz.driver.pdsuidomain.services.HypothesisService -- cgit v1.2.3 From 3c2a7fdfccc87cb8ec9e8e48c31c622555078c54 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 24 Oct 2017 11:33:55 +0700 Subject: Added field 'inclusion' to ExportTrialLabelCriterion --- .../export/trial/ExportTrialLabelCriterion.scala | 5 +- .../driver/pdsuidomain/fakes/entities/export.scala | 3 +- .../formats/json/sprayformats/export.scala | 86 ++++++++++++++++------ .../services/fake/FakeTrialService.scala | 3 +- .../json/sprayformats/ExportFormatSuite.scala | 12 ++- 5 files changed, 78 insertions(+), 31 deletions(-) (limited to 'src/main/scala/xyz/driver/pdsuidomain/services') diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala index 8376e34..98bd084 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialLabelCriterion.scala @@ -11,13 +11,14 @@ final case class ExportTrialLabelCriterion(criterionId: LongId[Criterion], armIds: Set[LongId[EligibilityArm]], criteria: String, isCompound: Boolean, - isDefining: Boolean) + isDefining: Boolean, + inclusion: Option[Boolean]) object ExportTrialLabelCriterion { implicit def toPhiString(x: ExportTrialLabelCriterion): PhiString = { import x._ phi"TrialLabelCriterion(criterionId=$criterionId, value=$value, labelId=$labelId, " + - phi"criteria=${Unsafe(criteria)}, isCompound=$isCompound, isDefining=$isDefining)" + phi"criteria=${Unsafe(criteria)}, isCompound=$isCompound, isDefining=$isDefining), inclusion=${Unsafe(inclusion)}" } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/export.scala b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/export.scala index 2c7d0e0..33da392 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/export.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/export.scala @@ -28,7 +28,8 @@ object export { armIds = setOf(nextLongId[EligibilityArm]), criteria = nextString(100), isCompound = nextBoolean(), - isDefining = nextBoolean() + isDefining = nextBoolean(), + inclusion = nextOption(nextBoolean()) ) def nextExportTrialWithLabels(): ExportTrialWithLabels = diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/export.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/export.scala index 4391453..9579288 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/export.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/export.scala @@ -3,6 +3,7 @@ package xyz.driver.pdsuidomain.formats.json.sprayformats import spray.json._ import xyz.driver.entities.labels.Label import xyz.driver.formats.json.labels._ +import xyz.driver.pdsuicommon.domain.LongId import xyz.driver.pdsuidomain.entities.export.patient._ import xyz.driver.pdsuidomain.entities.export.trial.{ExportTrialArm, ExportTrialLabelCriterion, ExportTrialWithLabels} import xyz.driver.pdsuidomain.entities.{Criterion, EligibilityArm} @@ -13,6 +14,14 @@ object export { import document._ import record._ + 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") + } + implicit val patientLabelEvidenceDocumentFormat: RootJsonFormat[ExportPatientLabelEvidenceDocument] = jsonFormat5(ExportPatientLabelEvidenceDocument.apply) @@ -29,6 +38,8 @@ object export { implicit val trialLabelCriterionFormat: RootJsonFormat[ExportTrialLabelCriterion] = new RootJsonFormat[ExportTrialLabelCriterion] { + implicit val className: String = "ExportTrialLabelCriterion" + override def write(obj: ExportTrialLabelCriterion): JsValue = JsObject( "value" -> obj.value @@ -43,40 +54,69 @@ object export { "criterionText" -> obj.criteria.toJson, "armIds" -> obj.armIds.toJson, "isCompound" -> obj.isCompound.toJson, - "isDefining" -> obj.isDefining.toJson + "isDefining" -> obj.isDefining.toJson, + "inclusion" -> obj.inclusion.toJson ) override def read(json: JsValue): ExportTrialLabelCriterion = { + json match { + case JsObject(fields) => + val value = fields + .get("value") + .map(_.convertTo[String]) + .map { + case "Yes" => Option(true) + case "No" => Option(false) + case "Unknown" => Option.empty[Boolean] + } + .getOrElse(deserializationErrorFieldMessage("value", json)) - val fields = Seq("value", "labelId", "criterionId", "criterionText", "armIds", "isCompound", "isDefining") - - json.asJsObject.getFields(fields: _*) match { - case Seq(JsString(valueString), - labelId, - criterionId, - JsString(criterionText), - JsArray(armIdsVector), - JsBoolean(isCompound), - JsBoolean(isDefining)) => - val value = valueString match { - case "Yes" => Option(true) - case "No" => Option(false) - case "Unknown" => Option.empty[Boolean] - } + val labelId = fields + .get("labelId") + .map(_.convertTo[LongId[Label]]) + .getOrElse(deserializationErrorFieldMessage("labelId", json)) + + val criterionId = fields + .get("criterionId") + .map(_.convertTo[LongId[Criterion]]) + .getOrElse(deserializationErrorFieldMessage("criterionId", json)) + + val criterionText = fields + .get("criterionText") + .map(_.convertTo[String]) + .getOrElse(deserializationErrorFieldMessage("criterionText", json)) + + val armIds = fields + .get("armIds") + .map(_.convertTo[Set[LongId[EligibilityArm]]]) + .getOrElse(deserializationErrorFieldMessage("armIds", json)) + + val isCompound = fields + .get("isCompound") + .map(_.convertTo[Boolean]) + .getOrElse(deserializationErrorFieldMessage("isCompound", json)) + + val isDefining = fields + .get("isDefining") + .map(_.convertTo[Boolean]) + .getOrElse(deserializationErrorFieldMessage("isDefining", json)) + + val inclusion = fields + .get("inclusion") + .flatMap(_.convertTo[Option[Boolean]]) ExportTrialLabelCriterion( - longIdFormat[Criterion].read(criterionId), + criterionId, value, - longIdFormat[Label].read(labelId), - armIdsVector.map(longIdFormat[EligibilityArm].read).toSet, + labelId, + armIds, criterionText, isCompound, - isDefining + isDefining, + inclusion ) - case _ => - deserializationError( - s"Cannot find required fields ${fields.mkString(", ")} in ExportTrialLabelCriterion object!") + case _ => deserializationErrorEntityMessage(json) } } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/fake/FakeTrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/fake/FakeTrialService.scala index e0efcd1..e23449c 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/fake/FakeTrialService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/fake/FakeTrialService.scala @@ -85,7 +85,8 @@ class FakeTrialService extends TrialService { generators.setOf(LongId[EligibilityArm](generators.nextInt(999999).toLong)), generators.nextName().value, generators.nextBoolean(), - generators.nextBoolean() + generators.nextBoolean(), + generators.nextOption(generators.nextBoolean()) )) ) diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/ExportFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/ExportFormatSuite.scala index d78e754..767f832 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/ExportFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/ExportFormatSuite.scala @@ -92,7 +92,8 @@ class ExportFormatSuite extends FlatSpec with Matchers { armIds = Set(LongId(1), LongId(2)), criteria = "criteria 10 text", isCompound = false, - isDefining = false + isDefining = false, + inclusion = Some(false) ), ExportTrialLabelCriterion( criterionId = LongId(11), @@ -101,7 +102,8 @@ class ExportFormatSuite extends FlatSpec with Matchers { armIds = Set(LongId(2)), criteria = "criteria 11 text", isCompound = true, - isDefining = false + isDefining = false, + inclusion = None ) ) val trialWithLabels = ExportTrialWithLabels( @@ -117,8 +119,10 @@ class ExportFormatSuite extends FlatSpec with Matchers { writtenJson should be( """{"nctId":"NCT000001","trialId":"40892a07-c638-49d2-9795-1edfefbbcc7c","lastReviewed":"2017-08-10T18:00Z", "labelVersion":1,"arms":[{"armId":1,"armName":"arm 1","diseaseList":["Breast"]},{"armId":2,"armName":"arm 2","diseaseList":["Breast"]}],"criteria":[ - {"value":"Yes","labelId":21,"criterionId":10,"criterionText":"criteria 10 text","armIds":[1,2],"isCompound":false,"isDefining":false}, - {"value":"Unknown","labelId":21,"criterionId":11,"criterionText":"criteria 11 text","armIds":[2],"isCompound":true,"isDefining":false}]}""".parseJson) + {"value":"Yes","labelId":21,"criterionId":10,"criterionText":"criteria 10 text","armIds":[1,2],"isCompound":false, + "isDefining":false,"inclusion":false}, + {"value":"Unknown","labelId":21,"criterionId":11,"criterionText":"criteria 11 text","armIds":[2],"isCompound":true, + "isDefining":false,"inclusion":null}]}""".parseJson) } } -- cgit v1.2.3 From bb4176645aed22311b372f8f7f4897f9012322bd Mon Sep 17 00:00:00 2001 From: vlad Date: Wed, 25 Oct 2017 19:18:48 -0700 Subject: Support for table names in filters called over REST --- src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/main/scala/xyz/driver/pdsuidomain/services') 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 0ff29ef..7275e3c 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestHelper.scala @@ -57,9 +57,9 @@ trait RestHelper { def exprToQuery(expr: SearchFilterExpr): Seq[(String, String)] = expr match { case SearchFilterExpr.Empty => Seq.empty case SearchFilterExpr.Atom.Binary(dimension, op, value) => - Seq("filters" -> s"${dimension.name} ${opToString(op)} $value") + Seq("filters" -> s"${dimension.tableName.fold("")(t => s"$t.") + dimension.name} ${opToString(op)} $value") case SearchFilterExpr.Atom.NAry(dimension, SearchFilterNAryOperation.In, values) => - Seq("filters" -> s"${dimension.name} in ${values.mkString(",")}") + Seq("filters" -> s"${dimension.tableName.fold("")(t => s"$t.") + dimension.name} in ${values.mkString(",")}") case SearchFilterExpr.Intersection(ops) => ops.flatMap(op => exprToQuery(op)) case expr => sys.error(s"No parser available for filter expression $expr.") -- cgit v1.2.3