diff --git a/tofhir-server/src/main/scala/io/tofhir/server/endpoint/JobEndpoint.scala b/tofhir-server/src/main/scala/io/tofhir/server/endpoint/JobEndpoint.scala index 09c990c7..1437a0e3 100644 --- a/tofhir-server/src/main/scala/io/tofhir/server/endpoint/JobEndpoint.scala +++ b/tofhir-server/src/main/scala/io/tofhir/server/endpoint/JobEndpoint.scala @@ -196,7 +196,7 @@ class JobEndpoint(jobRepository: IJobRepository, mappingRepository: IMappingRepo * */ private def getExecutions(projectId: String, id: String): Route = { get { - parameterMap { queryParams => // page is supported for now (e.g. page=1) + parameterMap { queryParams => // page and some filter options available. (page, dateBefore, dateAfter, errorStatuses, rowPerPage) onComplete(executionService.getExecutions(projectId, id, queryParams)) { case util.Success(response) => val headers = List( diff --git a/tofhir-server/src/main/scala/io/tofhir/server/endpoint/SchemaDefinitionEndpoint.scala b/tofhir-server/src/main/scala/io/tofhir/server/endpoint/SchemaDefinitionEndpoint.scala index d1be6114..46afe722 100644 --- a/tofhir-server/src/main/scala/io/tofhir/server/endpoint/SchemaDefinitionEndpoint.scala +++ b/tofhir-server/src/main/scala/io/tofhir/server/endpoint/SchemaDefinitionEndpoint.scala @@ -4,7 +4,7 @@ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route import com.typesafe.scalalogging.LazyLogging -import io.tofhir.common.model.SchemaDefinition +import io.tofhir.common.model.{SchemaDefinition, SimpleStructureDefinition} import io.tofhir.engine.Execution.actorSystem.dispatcher import io.tofhir.server.endpoint.SchemaDefinitionEndpoint.{SEGMENT_INFER, SEGMENT_REDCAP, SEGMENT_SCHEMAS} import io.tofhir.server.model.Json4sSupport._ @@ -15,6 +15,7 @@ import io.tofhir.engine.util.FhirMappingJobFormatter.formats import io.tofhir.server.common.model.{BadRequest, ResourceNotFound, ToFhirRestCall} import io.tofhir.server.endpoint.MappingContextEndpoint.ATTACHMENT import io.tofhir.server.service.mapping.IMappingRepository +import io.onfhir.api.Resource class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepository: IMappingRepository) extends LazyLogging { @@ -27,7 +28,7 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos parameterMap { queryParams => queryParams.get("url") match { case Some(url) => getSchemaByUrl(projectId, url) - case None => getAllSchemas(request) ~ createSchema(projectId) // Operations on all schemas + case None => getAllSchemas(request) ~ createSchema(projectId, queryParams.getOrElse("format", SchemaFormats.SIMPLE_STRUCTURE_DEFINITION)) // Operations on all schemas } } } ~ pathPrefix(SEGMENT_INFER) { // infer a schema @@ -46,12 +47,28 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos } } - private def createSchema(projectId: String): Route = { + /** + * Create a new schema with the given body + * @param projectId Id of the project in which the schemas will be created + * @param format format of the schema in the request, there are two options StructureDefinition and SimpleStructureDefinition + * @return the SchemaDefinition of the created schema + */ + private def createSchema(projectId: String, format: String): Route = { post { // Create a new schema definition - entity(as[SchemaDefinition]) { schemaDefinition => - complete { - service.createSchema(projectId, schemaDefinition) map { createdDefinition => - StatusCodes.Created -> createdDefinition + // If the schema is in the form of StructureDefinition, convert into SimpleStructureDefinition and save + if (format == SchemaFormats.STRUCTURE_DEFINITION) { + entity(as[Resource]) { schemaStructureDefinition => + complete { + service.createSchemaFromStructureDefinition(projectId, schemaStructureDefinition) + } + } + } + else{ + entity(as[SchemaDefinition]) { schemaDefinition => + complete { + service.createSchema(projectId, schemaDefinition) map { createdDefinition => + StatusCodes.Created -> createdDefinition + } } } } @@ -60,11 +77,27 @@ class SchemaDefinitionEndpoint(schemaRepository: ISchemaRepository, mappingRepos private def getSchema(projectId: String, id: String): Route = { get { - complete { - service.getSchema(projectId, id) map { - case Some(schemaDefinition) => StatusCodes.OK -> schemaDefinition - case None => StatusCodes.NotFound -> { - throw ResourceNotFound("Schema not found", s"Schema definition with name $id not found") + parameterMap { queryParams => + complete { + // Requested format of the schema: "StructureDefinition" or "SimpleStructureDefinition" + val format: String = queryParams.getOrElse("format", SchemaFormats.SIMPLE_STRUCTURE_DEFINITION) + // Send structure definition for the user to export + if(format == SchemaFormats.STRUCTURE_DEFINITION){ + service.getSchemaAsStructureDefinition(projectId, id) map { + case Some(schemaStructureDefinition) => StatusCodes.OK -> schemaStructureDefinition + case None => { + throw ResourceNotFound("Schema not found", s"Schema definition with name $id not found") + } + } + } + // Send simple structure definition for general use in frontend + else { + service.getSchema(projectId, id) map { + case Some(schemaSimpleStructureDefinition) => StatusCodes.OK -> schemaSimpleStructureDefinition + case None => StatusCodes.NotFound -> { + throw ResourceNotFound("Schema not found", s"Schema definition with name $id not found") + } + } } } } @@ -147,3 +180,11 @@ object SchemaDefinitionEndpoint { val SEGMENT_REDCAP = "redcap" } +/** + * The schema formats available for POST and GET schema methods + */ +object SchemaFormats{ + val STRUCTURE_DEFINITION = "StructureDefinition" + val SIMPLE_STRUCTURE_DEFINITION = "SimpleStructureDefinition" +} + diff --git a/tofhir-server/src/main/scala/io/tofhir/server/service/SchemaDefinitionService.scala b/tofhir-server/src/main/scala/io/tofhir/server/service/SchemaDefinitionService.scala index 26195aab..acdb9c72 100644 --- a/tofhir-server/src/main/scala/io/tofhir/server/service/SchemaDefinitionService.scala +++ b/tofhir-server/src/main/scala/io/tofhir/server/service/SchemaDefinitionService.scala @@ -14,6 +14,7 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import akka.stream.scaladsl.Source import akka.util.ByteString +import io.onfhir.api.Resource import io.tofhir.engine.util.{CsvUtil, RedCapUtil} import io.tofhir.server.common.model.{BadRequest, ResourceNotFound} @@ -143,4 +144,24 @@ class SchemaDefinitionService(schemaRepository: ISchemaRepository, mappingReposi Future.sequence(definitions.map(definition => schemaRepository.saveSchema(projectId, definition))) }) } + + /** + * Get structure definition resource of the schema + * @param projectId project containing the schema definition + * @param id id of the requested schema + * @return Structure definition of the schema converted into StructureDefinition Resource + */ + def getSchemaAsStructureDefinition(projectId: String, id: String): Future[Option[Resource]] = { + schemaRepository.getSchemaAsStructureDefinition(projectId, id) + } + + /** + * Save the schema by using its StructureDefinition + * @param projectId Identifier of the project in which the schema will be created + * @param structureDefinitionResource schema definition in the form of Structure Definition resource + * @return the SchemaDefinition of the created schema + */ + def createSchemaFromStructureDefinition(projectId: String, structureDefinitionResource: Resource): Future[SchemaDefinition] = { + schemaRepository.saveSchemaByStructureDefinition(projectId, structureDefinitionResource) + } } diff --git a/tofhir-server/src/main/scala/io/tofhir/server/service/schema/ISchemaRepository.scala b/tofhir-server/src/main/scala/io/tofhir/server/service/schema/ISchemaRepository.scala index ad4a7fa3..b3fea350 100644 --- a/tofhir-server/src/main/scala/io/tofhir/server/service/schema/ISchemaRepository.scala +++ b/tofhir-server/src/main/scala/io/tofhir/server/service/schema/ISchemaRepository.scala @@ -1,5 +1,6 @@ package io.tofhir.server.service.schema +import io.onfhir.api.Resource import io.tofhir.common.model.SchemaDefinition import io.tofhir.engine.mapping.IFhirSchemaLoader @@ -70,4 +71,23 @@ trait ISchemaRepository extends IFhirSchemaLoader { */ def deleteProjectSchemas(projectId: String): Unit + /** + * Retrieve the Structure Definition of the schema identified by its id. + * + * @param projectId Project containing the schema definition + * @param id Identifier of the schema definition + * @return Structure definition of the schema converted into StructureDefinition Resource + */ + def getSchemaAsStructureDefinition(projectId: String, id: String): Future[Option[Resource]] + + + /** + * Save the schema by using its Structure Definition + * + * @param projectId Project containing the schema definition + * @param structureDefinitionResource The resource of the structure definition + * @return the SchemaDefinition of the created schema + */ + def saveSchemaByStructureDefinition(projectId: String, structureDefinitionResource: Resource): Future[SchemaDefinition] + } diff --git a/tofhir-server/src/main/scala/io/tofhir/server/service/schema/SchemaFolderRepository.scala b/tofhir-server/src/main/scala/io/tofhir/server/service/schema/SchemaFolderRepository.scala index 2ee03e6e..9dd61eae 100644 --- a/tofhir-server/src/main/scala/io/tofhir/server/service/schema/SchemaFolderRepository.scala +++ b/tofhir-server/src/main/scala/io/tofhir/server/service/schema/SchemaFolderRepository.scala @@ -24,6 +24,7 @@ import org.json4s.{Extraction, JBool, JObject} import java.io.{File, FileWriter} import java.nio.charset.StandardCharsets +import java.util.UUID import scala.collection.mutable import scala.concurrent.Future import scala.io.Source @@ -117,31 +118,10 @@ class SchemaFolderRepository(schemaRepositoryFolderPath: String, projectFolderRe throw BadRequest("Schema definition is not valid.", s"Schema definition cannot be validated: ${schemaDefinition.url}", Some(e)) } - if (schemaDefinitions.contains(projectId) && schemaDefinitions(projectId).contains(schemaDefinition.id)) { - throw AlreadyExists("Schema already exists.", s"A schema definition with id ${schemaDefinition.id} already exists in the schema repository at ${FileUtils.getPath(schemaRepositoryFolderPath).toAbsolutePath.toString}") - } - - // Check if the url already exists - val schemaUrls: Map[String, String] = schemaDefinitions.values.flatMap(_.values).map(schema => schema.url -> schema.name).toMap - if (schemaUrls.contains(schemaDefinition.url)) { - throw AlreadyExists("Schema already exists.", s"A schema definition with url ${schemaDefinition.url} already exists. Check the schema '${schemaUrls(schemaDefinition.url)}'") - } - - // Write to the repository as a new file - getFileForSchema(projectId, schemaDefinition.id).map(newFile => { - val fw = new FileWriter(newFile) - fw.write(structureDefinitionResource.toPrettyJson) - fw.close() + checkIfSchemaIsUnique(projectId, schemaDefinition.id, schemaDefinition.url) - // Update the project with the schema - projectFolderRepository.addSchema(projectId, schemaDefinition) - - // Update the caches with the new schema - baseFhirConfig.profileRestrictions += schemaDefinition.url -> fhirFoundationResourceParser.parseStructureDefinition(structureDefinitionResource, includeElementMetadata = true) - schemaDefinitions.getOrElseUpdate(projectId, mutable.Map.empty).put(schemaDefinition.id, schemaDefinition) - - schemaDefinition - }) + // Write to the repository as a new file and update caches + writeSchemaAndUpdateCaches(projectId, structureDefinitionResource, schemaDefinition) } /** @@ -354,5 +334,100 @@ class SchemaFolderRepository(schemaRepositoryFolderPath: String, projectFolderRe } baseFhirConfig } + + /** + * Retrieve the Structure Definition of the schema identified by its id. + * + * @param projectId Project containing the schema definition + * @param id Identifier of the schema definition + * @return Structure definition of the schema converted into StructureDefinition Resource + */ + override def getSchemaAsStructureDefinition(projectId: String, id: String): Future[Option[Resource]] = { + getSchema(projectId, id).map { + case Some(schemaStructureDefinition) => + Some(SchemaUtil.convertToStructureDefinitionResource(schemaStructureDefinition)) + case None => + None + } + } + + /** + * Save a schema by using directly its structure definition resource + * Throws: + * InitializationException – If there is a problem in given profile or value set definitions of the schema structure definition + * + * @param projectId Identifier of the project in which the schema will be created + * @param structureDefinitionResource Structure definition resource of the schema + * @return the SchemaDefinition of the created schema + */ + override def saveSchemaByStructureDefinition(projectId: String, structureDefinitionResource: Resource): Future[SchemaDefinition] = { + // Validate the resource + try { + fhirConfigurator.validateGivenInfrastructureResources(baseFhirConfig, api.FHIR_FOUNDATION_RESOURCES.FHIR_STRUCTURE_DEFINITION, Seq(structureDefinitionResource)) + } catch { + case e: Exception => + throw BadRequest("Schema resource is not valid.", s"Schema resource cannot be validated.", Some(e)) + } + + // Create structureDefinition from the resource + val structureDefinition: ProfileRestrictions = fhirFoundationResourceParser.parseStructureDefinition(structureDefinitionResource, includeElementMetadata = true) + // Generate an Id if id is missing + val schemaId = structureDefinition.id.getOrElse(UUID.randomUUID().toString) + + checkIfSchemaIsUnique(projectId, schemaId, structureDefinition.url) + + // To use convertToSchemaDefinition, profileRestrictions sequence must include the structure definition. Add it before conversion + baseFhirConfig.profileRestrictions += structureDefinition.url -> structureDefinition + val schemaDefinition = convertToSchemaDefinition(structureDefinition, simpleStructureDefinitionService) + // Remove structure definition from the cache and add it after file writing is done to ensure files and cache are the same + baseFhirConfig.profileRestrictions -= structureDefinition.url + + // Write to the repository as a new file and update caches + writeSchemaAndUpdateCaches(projectId, structureDefinitionResource, schemaDefinition) + } + + /** + * Checks if ID of the schema is unique in the project and url is unique in the program + * Throws: + * {@link AlreadyExists} with the code 409 if the schema id is not unique in the project or the schema url is not unique in the program + * + * @param projectId Identifier of the project to check schema ID's in it + * @param schemaId Identifier of the schema + * @param schemaUrl Url of the schema + */ + private def checkIfSchemaIsUnique(projectId: String, schemaId: String, schemaUrl: String): Unit = { + if (schemaDefinitions.contains(projectId) && schemaDefinitions(projectId).contains(schemaId)) { + throw AlreadyExists("Schema already exists.", s"A schema definition with id ${schemaId} already exists in the schema repository at ${FileUtils.getPath(schemaRepositoryFolderPath).toAbsolutePath.toString}") + } + + val schemaUrls: Map[String, String] = schemaDefinitions.values.flatMap(_.values).map(schema => schema.url -> schema.name).toMap + if (schemaUrls.contains(schemaUrl)) { + throw AlreadyExists("Schema already exists.", s"A schema definition with url ${schemaUrl} already exists. Check the schema '${schemaUrls(schemaUrl)}'") + } + } + + /** + * Write the schema file and update the caches accordingly + * @param projectId Id of the project that will include the schema + * @param structureDefinitionResource Schema resource that will be written + * @param schemaDefinition Definition of the schema + * @return + */ + private def writeSchemaAndUpdateCaches(projectId: String, structureDefinitionResource: Resource, schemaDefinition: SchemaDefinition ): Future[SchemaDefinition] = { + getFileForSchema(projectId, schemaDefinition.id).map(newFile => { + val fw = new FileWriter(newFile) + fw.write(structureDefinitionResource.toPrettyJson) + fw.close() + + // Update the project with the schema + projectFolderRepository.addSchema(projectId, schemaDefinition) + + // Update the caches with the new schema + baseFhirConfig.profileRestrictions += schemaDefinition.url -> fhirFoundationResourceParser.parseStructureDefinition(structureDefinitionResource, includeElementMetadata = true) + schemaDefinitions.getOrElseUpdate(projectId, mutable.Map.empty).put(schemaDefinition.id, schemaDefinition) + + schemaDefinition + }) + } } diff --git a/tofhir-server/src/test/resources/blood-pressure.json b/tofhir-server/src/test/resources/blood-pressure.json new file mode 100644 index 00000000..7cb16920 --- /dev/null +++ b/tofhir-server/src/test/resources/blood-pressure.json @@ -0,0 +1,112 @@ +{ + "id": "blood-pressure-schema", + "resourceType": "StructureDefinition", + "url": "https://aiccelerate.eu/fhir/StructureDefinition/Ext-plt1-hsjd-blood-pressure", + "name": "blood-pressure-schema", + "status": "draft", + "fhirVersion": "4.0.1", + "kind": "logical", + "abstract": false, + "type": "Ext-plt1-hsjd-blood-pressure", + "baseDefinition": "http://hl7.org/fhir/StructureDefinition/Element", + "derivation": "specialization", + "differential": { + "element": [ + { + "id": "Ext-plt1-hsjd-blood-pressure", + "path": "Ext-plt1-hsjd-blood-pressure", + "min": 0, + "max": "*", + "type": [ + { + "code": "Element" + } + ] + }, + { + "id": "Ext-plt1-hsjd-blood-pressure.pid", + "path": "Ext-plt1-hsjd-blood-pressure.pid", + "min": 1, + "max": "1", + "type": [ + { + "code": "id", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/id" + ] + } + ] + }, + { + "id": "Ext-plt1-hsjd-blood-pressure.time", + "path": "Ext-plt1-hsjd-blood-pressure.time", + "min": 1, + "max": "1", + "type": [ + { + "code": "dateTime", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/dateTime" + ] + } + ] + }, + { + "id": "Ext-plt1-hsjd-blood-pressure.unit", + "path": "Ext-plt1-hsjd-blood-pressure.unit", + "min": 0, + "max": "1", + "type": [ + { + "code": "code", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/code" + ] + } + ] + }, + { + "id": "Ext-plt1-hsjd-blood-pressure.episodeId", + "path": "Ext-plt1-hsjd-blood-pressure.episodeId", + "min": 0, + "max": "1", + "type": [ + { + "code": "id", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/id" + ] + } + ] + }, + { + "id": "Ext-plt1-hsjd-blood-pressure.systolic", + "path": "Ext-plt1-hsjd-blood-pressure.systolic", + "min": 0, + "max": "1", + "type": [ + { + "code": "string", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/string" + ] + } + ] + }, + { + "id": "Ext-plt1-hsjd-blood-pressure.diastolic", + "path": "Ext-plt1-hsjd-blood-pressure.diastolic", + "min": 0, + "max": "1", + "type": [ + { + "code": "string", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/string" + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tofhir-server/src/test/scala/io/tofhir/server/project/SchemaEndpointTest.scala b/tofhir-server/src/test/scala/io/tofhir/server/project/SchemaEndpointTest.scala index e5b30b99..db204b19 100644 --- a/tofhir-server/src/test/scala/io/tofhir/server/project/SchemaEndpointTest.scala +++ b/tofhir-server/src/test/scala/io/tofhir/server/project/SchemaEndpointTest.scala @@ -3,7 +3,7 @@ package io.tofhir.server.project import akka.actor.ActorSystem import akka.http.scaladsl.model.{ContentTypes, HttpEntity, Multipart, StatusCodes} import akka.http.scaladsl.testkit.RouteTestTimeout -import io.onfhir.api.FHIR_FOUNDATION_RESOURCES +import io.onfhir.api.{FHIR_FOUNDATION_RESOURCES, Resource} import io.tofhir.common.model.{DataTypeWithProfiles, SchemaDefinition, SimpleStructureDefinition} import io.tofhir.engine.model.{FhirMapping, FhirMappingSource, FileSystemSource, FileSystemSourceSettings, SqlSource, SqlSourceSettings} import io.tofhir.engine.util.FileUtils @@ -15,6 +15,8 @@ import org.json4s.jackson.JsonMethods import org.json4s.jackson.Serialization.writePretty import io.tofhir.engine.util.FhirMappingJobFormatter.formats import io.tofhir.engine.util.FileUtils.FileExtensions +import io.tofhir.server.endpoint.SchemaFormats + import java.io.File import java.sql.{Connection, DriverManager, Statement} import scala.io.{BufferedSource, Source} @@ -76,7 +78,6 @@ class SchemaEndpointTest extends BaseEndpointTest { // fifth schema with the same url as the second schema, to be rejected val schema5: SchemaDefinition = SchemaDefinition(url = "https://example.com/fhir/StructureDefinition/schema2", `type` = "ty5", name = "name5", rootDefinition = None, fieldDefinitions = None) - // mapping using schema2 val mapping: FhirMapping = FhirMapping(id = "mapping", url = "http://example.com/mapping", name = "mapping", source = Seq(FhirMappingSource(alias="test",url = "https://example.com/fhir/StructureDefinition/schema2")), context = Map.empty, mapping = Seq.empty) @@ -378,6 +379,76 @@ class SchemaEndpointTest extends BaseEndpointTest { response should include("Type: https://tofhir.io/errors/BadRequest") } } + + "get structure definition of a schema to export" in { + // get a schema structure definition by defining format parameter + Get(s"/tofhir/projects/${projectId}/schemas/${schema3.id}?format=${SchemaFormats.STRUCTURE_DEFINITION}") ~> route ~> check { + status shouldEqual StatusCodes.OK + // validate the retrieved schema + val schemaResource: Resource = JsonMethods.parse(responseAs[String]).extract[Resource] + // Create a map from resource json + var schemaResourceMap: Map[String, Any] = Map.empty + schemaResource.values.foreach((tuple) => schemaResourceMap += tuple._1 -> tuple._2) + // Validate some fields of the schema + schemaResourceMap should contain allOf( + "id" -> schema3.id, + "url" -> schema3.url, + "name" -> schema3.name, + "resourceType" -> SchemaFormats.STRUCTURE_DEFINITION, + "type" -> schema3.`type`, + "fhirVersion" -> "4.0.1", + "kind" -> "logical", + "baseDefinition" -> "http://hl7.org/fhir/StructureDefinition/Element", + "derivation" -> "specialization", + "status" -> "draft", + "abstract" -> false + ) + + // Extract the elements of the StructureDefinition to a List + val differential: Map[String, List[Any]] = schemaResourceMap.get("differential") match { + case Some(value) => value.asInstanceOf[Map[String, List[Any]]] + case None => Map.empty + } + // Validate if fieldDefinitions length of the SchemaDefinition and element length of the StructureDefinition are the same + differential.get("element") match { + case Some(elementList) => elementList should have length(3) + case None => fail("Field definitions are missing in the structure definition of the schema.") + } + } + + // get a schema with invalid id by a url including format parameter + Get(s"/tofhir/projects/${projectId}/schemas/123123?format=${SchemaFormats.STRUCTURE_DEFINITION}") ~> route ~> check { + status shouldEqual StatusCodes.NotFound + } + } + + "import schema as structure definition" in { + // blood pressure schema information for the test + val bloodPressureSchemaUrl: String = "https://aiccelerate.eu/fhir/StructureDefinition/Ext-plt1-hsjd-blood-pressure" + val bloodPressureSchemaName: String = "blood-pressure-schema" + + // read the StructureDefinition of the schema from file + val schemaResource: Some[Resource] = Some(FileOperations.readJsonContentAsObject[Resource](FileOperations.getFileIfExists(getClass.getResource("/blood-pressure.json").getPath))) + + // create a schema by using StructureDefinition + Post(s"/tofhir/projects/${projectId}/schemas?format=${SchemaFormats.STRUCTURE_DEFINITION}", HttpEntity(ContentTypes.`application/json`, writePretty(schemaResource))) ~> route ~> check { + status shouldEqual StatusCodes.OK + } + + // validate if the schema is imported correctly + Get(s"/tofhir/projects/${projectId}/schemas?url=${bloodPressureSchemaUrl}") ~> route ~> check { + status shouldEqual StatusCodes.OK + // validate the retrieved schema + val schema: SchemaDefinition = JsonMethods.parse(responseAs[String]).extract[SchemaDefinition] + schema.url shouldEqual bloodPressureSchemaUrl + schema.name shouldEqual bloodPressureSchemaName + val fieldDefinitions = schema.fieldDefinitions.get + fieldDefinitions.size shouldEqual 6 + fieldDefinitions.head.path shouldEqual "Ext-plt1-hsjd-blood-pressure.pid" + fieldDefinitions.last.path shouldEqual "Ext-plt1-hsjd-blood-pressure.diastolic" + } + } + } /**