From 17fcc032f74d82e4c6384c317ec36bbbbd843366 Mon Sep 17 00:00:00 2001 From: emrecam Date: Mon, 30 Sep 2024 19:08:56 +0300 Subject: [PATCH] :recycle: Refactor: Use FHIR_DATA_TYPES instead of hardcoded values of data types --- .../common/util/CustomMappingFunctions.scala | 3 ++- .../fhirPath/FhirPathMappingFunctions.scala | 7 +++--- .../rxnorm/RxNormApiFunctionLibrary.scala | 13 ++++++----- .../server/endpoint/SchemaEndpointTest.scala | 22 +++++++++---------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala b/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala index da74e49e..5ce9243d 100644 --- a/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala +++ b/tofhir-common/src/main/scala/io/tofhir/common/util/CustomMappingFunctions.scala @@ -1,5 +1,6 @@ package io.tofhir.common.util +import io.onfhir.api.FHIR_DATA_TYPES import io.onfhir.path.annotation.FhirPathFunction import io.onfhir.path.grammar.FhirPathExprParser.ExpressionContext import io.onfhir.path.{AbstractFhirPathFunctionLibrary, FhirPathEnvironment, FhirPathException, FhirPathExpressionEvaluator, FhirPathResult, FhirPathString, IFhirPathFunctionLibraryFactory} @@ -24,7 +25,7 @@ class CustomMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPath * @return Space separated numbers concatenated in a string */ @FhirPathFunction(documentation = "\uD83D\uDCDC Decodes the given data and converts it to an array of space separated numbers. Returns the space separated numbers concatenated in a string.\n\n\uD83D\uDCDD _@param_ **`dataExpr`** \nString data such that byte representation of each 2 consecutive characters represents a number.\n\n\uD83D\uDD19 _@return_ \n```\n'123 456 123'\n``` \n\uD83D\uDCA1 **E.g.** cst:createTimeSeriesData(%data)", - insertText = "cst:createTimeSeriesData()", detail = "cst", label = "cst:createTimeSeriesData", kind = "Method", returnType = Seq("string"), inputType = Seq("string")) + insertText = "cst:createTimeSeriesData()", detail = "cst", label = "cst:createTimeSeriesData", kind = "Method", returnType = Seq(FHIR_DATA_TYPES.STRING), inputType = Seq(FHIR_DATA_TYPES.STRING)) def createTimeSeriesData(dataExpr: ExpressionContext): Seq[FhirPathResult] = { val dataResult = new FhirPathExpressionEvaluator(context, current).visit(dataExpr) if (dataResult.length > 1 || !dataResult.head.isInstanceOf[FhirPathString]) { diff --git a/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala b/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala index ae137c7c..afbadd2a 100644 --- a/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala +++ b/tofhir-engine/src/main/scala/io/tofhir/engine/mapping/fhirPath/FhirPathMappingFunctions.scala @@ -1,5 +1,6 @@ package io.tofhir.engine.mapping.fhirPath +import io.onfhir.api.FHIR_DATA_TYPES import io.onfhir.api.util.FHIRUtil import io.onfhir.path._ import io.onfhir.path.annotation.FhirPathFunction @@ -30,7 +31,7 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa detail = "mpp", label = "mpp:getHashedId", kind = "Function", - returnType = Seq("string"), + returnType = Seq(FHIR_DATA_TYPES.STRING), inputType = Seq() ) def getHashedId(resourceTypeExp:ExpressionContext, inputExpr:ExpressionContext):Seq[FhirPathResult] = { @@ -79,7 +80,7 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa */ @FhirPathFunction(documentation = "\uD83D\uDCDC Creates a sequence of indices between from-to integers and concatenates them and prefix string to generate looped field names (i.e. prefix+from,...,prefix+to). After that, for each field, it checks whether it has a value or not and returns the list of field names which have values i.e the non-empty ones.\n\n\uD83D\uDCDD _@param_ **`prefixExpr`** \nPrefix string to be used to generate field names.\n\n\uD83D\uDCDD _@param_ **`fromExpr`** \nThe start index (inclusive).\n\n\uD83D\uDCDD _@param_ **`toExpr`** \nThe end index (inclusive).\n\n\uD83D\uDD19 _@return_ \n```\n[\"child_1\", \"child_3\", \"child_4\"]\n``` \n\uD83D\uDCA1 **E.g.** mpp:nonEmptyLoopedFields('child_',1,5)", insertText = "mpp:nonEmptyLoopedFields(, , )",detail = "mpp", label = "mpp:nonEmptyLoopedFields", kind = "Function", - returnType = Seq("String"), inputType = Seq()) + returnType = Seq(FHIR_DATA_TYPES.STRING), inputType = Seq()) def nonEmptyLoopedFields(prefixExpr: ExpressionContext, fromExpr: ExpressionContext, toExpr: ExpressionContext): Seq[FhirPathResult] = { val prefix = new FhirPathExpressionEvaluator(context, current).visit(prefixExpr) if (prefix.length != 1 || !prefix.forall(_.isInstanceOf[FhirPathString])) @@ -170,7 +171,7 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa */ @FhirPathFunction(documentation = "\uD83D\uDCDC Get corresponding value from the given concept map with the given key and column name. If there is no concept found with given key (code), return empty. It returns a list of string. \n⚠\uFE0F A mapping concept with specified reference **must** be registered to the mapping as a context to use this function.\n\n\uD83D\uDCDD _@param_ **`conceptMap`** \nA reference to the concept map context e.g. %obsConceptMap\n\n\uD83D\uDCDD _@param_ **`source_code`** \nSource code to perform lookup operation in the concept map e.g. code\n\n\uD83D\uDCDD _@param_ **`columnName`** \nFHIRPath string literal providing the name of the interested column from the concept map context.\n\n\uD83D\uDD19 _@return_ \n```json\n[\n \"target-column-value-1\",\n ...\n]\n``` \n\uD83D\uDCA1 **E.g.** mpp:getConcept(%obsConceptMap, code, 'target_code')", insertText = "mpp:getConcept(<%conceptMap>, , )", detail = "mpp", label = "mpp:getConcept" - , kind = "Function", returnType = Seq("string"), inputType = Seq()) + , kind = "Function", returnType = Seq(FHIR_DATA_TYPES.STRING), inputType = Seq()) def getConcept(conceptMap: ExpressionContext, keyExpr: ExpressionContext, columnName: ExpressionContext): Seq[FhirPathResult] = { val mapName = conceptMap.getText.substring(1) // skip the leading % character val conceptMapContext = getConceptMap(mapName) diff --git a/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala b/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala index b7ea91bb..3bd60721 100644 --- a/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala +++ b/tofhir-rxnorm/src/main/scala/io/tofhir/rxnorm/RxNormApiFunctionLibrary.scala @@ -1,5 +1,6 @@ package io.tofhir.rxnorm +import io.onfhir.api.FHIR_DATA_TYPES import io.onfhir.api.util.FHIRUtil import io.onfhir.path.annotation.FhirPathFunction import io.onfhir.path.grammar.FhirPathExprParser.ExpressionContext @@ -27,8 +28,8 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat detail = "rxn", label = "rxn:findRxConceptIdByNdc", kind = "Function", - returnType = Seq("string"), - inputType = Seq("string") + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def findRxConceptIdsByNdc(ndcExpr:ExpressionContext):Seq[FhirPathResult] = { val ndc: Option[String] = @@ -59,7 +60,7 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat label = "rxn:getMedicationDetails", kind = "Function", returnType = Seq("complex"), - inputType = Seq("string") + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def getMedicationDetails(rxcuiExpr:ExpressionContext):Seq[FhirPathResult] = { val conceptIds = evaluator.visit(rxcuiExpr) @@ -136,7 +137,7 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat label = "rxn:findIngredientsOfDrug", kind = "Function", returnType = Seq("complex"), - inputType = Seq("string") + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def findIngredientsOfDrug(rxcuiExpr:ExpressionContext):Seq[FhirPathResult] = { val rxcui: Option[String] = @@ -162,8 +163,8 @@ class RxNormApiFunctionLibrary(rxNormApiClient:RxNormApiClient, context: FhirPat detail = "rxn", label = "rxn:getATC", kind = "Function", - returnType = Seq("string"), - inputType = Seq("string") + returnType = Seq(FHIR_DATA_TYPES.STRING), + inputType = Seq(FHIR_DATA_TYPES.STRING) ) def getATC(rxcuiExpr:ExpressionContext): Seq[FhirPathResult] = { val rxcui: String = diff --git a/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala b/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala index a80d0391..ecd87a14 100644 --- a/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala +++ b/tofhir-server/src/test/scala/io/tofhir/server/endpoint/SchemaEndpointTest.scala @@ -4,7 +4,7 @@ import akka.actor.ActorSystem import akka.http.scaladsl.model.{ContentType, ContentTypes, HttpEntity, MediaTypes, Multipart, StatusCodes} import akka.http.scaladsl.testkit.RouteTestTimeout import io.onfhir.api.client.FhirBatchTransactionRequestBuilder -import io.onfhir.api.{FHIR_FOUNDATION_RESOURCES, Resource} +import io.onfhir.api.{FHIR_DATA_TYPES, FHIR_FOUNDATION_RESOURCES, Resource} import io.tofhir.OnFhirTestContainer import io.onfhir.definitions.common.model.{DataTypeWithProfiles, SchemaDefinition, SimpleStructureDefinition} import io.tofhir.engine.model._ @@ -52,12 +52,12 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schema3: SchemaDefinition = SchemaDefinition(url = "https://example.com/fhir/StructureDefinition/schema3", version = SchemaDefinition.VERSION_LATEST, `type` = "Ty3", name = "name3", description = Some("description3"), rootDefinition = None, fieldDefinitions = Some( Seq( SimpleStructureDefinition(id = "element-with-definition", - path = "Ty3.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty3.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-definition"), definition = Some("element definition"), comment = None, elements = None), SimpleStructureDefinition(id = "element-with-no-definition", - path = "Ty3.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty3.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-no-definition"), definition = None, comment = None, elements = None) @@ -70,12 +70,12 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schema4: SchemaDefinition = SchemaDefinition(url = "https://example.com/fhir/StructureDefinition/schema4", version = SchemaDefinition.VERSION_LATEST, `type` = "Ty4", name = "name4", description = Some("description4"), rootDefinition = None, fieldDefinitions = Some( Seq( SimpleStructureDefinition(id = "element-with-short", - path = "Ty4.element-with-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty4.element-with-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-short"), definition = None, comment = None, elements = None), SimpleStructureDefinition(id = "element-with-no-short", - path = "Ty4.element-with-no-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty4.element-with-no-short", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = None, definition = None, comment = None, elements = None) @@ -245,10 +245,10 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schema: SchemaDefinition = JsonMethods.parse(responseAs[String]).extract[SchemaDefinition] val fieldDefinitions = schema.fieldDefinitions.get fieldDefinitions.size shouldEqual 4 - fieldDefinitions.head.dataTypes.get.head.dataType shouldEqual "integer" - fieldDefinitions(1).dataTypes.get.head.dataType shouldEqual "date" - fieldDefinitions(2).dataTypes.get.head.dataType shouldEqual "dateTime" - fieldDefinitions(3).dataTypes.get.head.dataType shouldEqual "string" + fieldDefinitions.head.dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.INTEGER + fieldDefinitions(1).dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.DATE + fieldDefinitions(2).dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.DATETIME + fieldDefinitions(3).dataTypes.get.head.dataType shouldEqual FHIR_DATA_TYPES.STRING } } @@ -626,12 +626,12 @@ class SchemaEndpointTest extends BaseEndpointTest with OnFhirTestContainer { val schemaUrl: String = "https://example.com/fhir/StructureDefinition/schema" val schema: SchemaDefinition = SchemaDefinition(url = schemaUrl, version = "1.4.2", `type` = "Ty", name = "name", description = Some("description"), rootDefinition = None, fieldDefinitions = Some(Seq( SimpleStructureDefinition(id = "element-with-definition", - path = "Ty.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty.element-with-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-definition"), definition = Some("element definition"), comment = None, elements = None), SimpleStructureDefinition(id = "element-with-no-definition", - path = "Ty.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = "canonical", profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, + path = "Ty.element-with-no-definition", dataTypes = Some(Seq(DataTypeWithProfiles(dataType = FHIR_DATA_TYPES.CANONICAL, profiles = Some(Seq("http://hl7.org/fhir/StructureDefinition/canonical"))))), isPrimitive = true, isChoiceRoot = false, isArray = false, minCardinality = 0, maxCardinality = None, boundToValueSet = None, isValueSetBindingRequired = None, referencableProfiles = None, constraintDefinitions = None, sliceDefinition = None, sliceName = None, fixedValue = None, patternValue = None, referringTo = None, short = Some("element-with-no-definition"), definition = None, comment = None, elements = None)