diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/FhirDataTypesUtil.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/FhirDataTypesUtil.kt new file mode 100644 index 0000000000..a0a8e29be8 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/FhirDataTypesUtil.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2024 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.util + +import org.hl7.fhir.r4.formats.JsonParser +import org.hl7.fhir.r4.model.BooleanType +import org.hl7.fhir.r4.model.DateTimeType +import org.hl7.fhir.r4.model.DateType +import org.hl7.fhir.r4.model.DecimalType +import org.hl7.fhir.r4.model.Enumerations.DataType +import org.hl7.fhir.r4.model.IntegerType +import org.hl7.fhir.r4.model.StringType +import org.hl7.fhir.r4.model.TimeType +import org.hl7.fhir.r4.model.Type +import org.hl7.fhir.r4.model.UriType +import org.hl7.fhir.r4.utils.TypesUtilities +import timber.log.Timber + +private val fhirTypesJsonParser: JsonParser = JsonParser() + +private fun String.castToFhirPrimitiveType(type: DataType): Type? = + when (type) { + DataType.BOOLEAN -> BooleanType(this) + DataType.DECIMAL -> DecimalType(this) + DataType.INTEGER -> IntegerType(this) + DataType.DATE -> DateType(this) + DataType.DATETIME -> DateTimeType(this) + DataType.TIME -> TimeType(this) + DataType.STRING -> StringType(this) + DataType.URI -> UriType(this) + else -> null + } + +private fun String.castToFhirDataType(type: DataType): Type? = + try { + fhirTypesJsonParser.parseType(this, type.toCode()) + } catch (ex: Exception) { + Timber.e("Error casting \"$this\" to FHIR type \"${type.toCode()}\"") + throw ex + } + +/** Cast string value (including json string) to the FHIR {@link org.hl7.fhir.r4.model.Type} */ +fun String.castToType(type: DataType): Type? { + return if (TypesUtilities.isPrimitive(type.toCode())) { + castToFhirPrimitiveType(type) + } else { + castToFhirDataType(type) + } +} diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt index cbb925e223..cd2ffafc4c 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtension.kt @@ -20,23 +20,13 @@ import com.google.android.fhir.datacapture.extensions.asStringValue import com.google.android.fhir.datacapture.extensions.logicalId import com.google.android.fhir.datacapture.extensions.targetStructureMap import java.util.Locale -import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.Coding -import org.hl7.fhir.r4.model.DateTimeType -import org.hl7.fhir.r4.model.DateType -import org.hl7.fhir.r4.model.DecimalType -import org.hl7.fhir.r4.model.Enumerations.DataType import org.hl7.fhir.r4.model.Expression import org.hl7.fhir.r4.model.IdType -import org.hl7.fhir.r4.model.IntegerType -import org.hl7.fhir.r4.model.Quantity import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.StringType -import org.hl7.fhir.r4.model.TimeType -import org.hl7.fhir.r4.model.Type -import org.hl7.fhir.r4.model.UriType import org.smartregister.fhircore.engine.configuration.LinkIdType import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig import org.smartregister.fhircore.engine.configuration.UniqueIdAssignmentConfig @@ -45,6 +35,7 @@ import org.smartregister.fhircore.engine.domain.model.ActionParameterType import org.smartregister.fhircore.engine.domain.model.RuleConfig import org.smartregister.fhircore.engine.domain.model.isEditable import org.smartregister.fhircore.engine.domain.model.isReadOnly +import org.smartregister.fhircore.engine.util.castToType fun QuestionnaireResponse.QuestionnaireResponseItemComponent.asLabel() = if (this.linkId != null) { @@ -301,20 +292,3 @@ suspend fun Questionnaire.prepopulateUniqueIdAssignment( } } } - -/** Cast string value (including json string) to the FHIR {@link org.hl7.fhir.r4.model.Type} */ -fun String.castToType(type: DataType): Type? { - return when (type) { - DataType.BOOLEAN -> BooleanType(this) - DataType.DECIMAL -> DecimalType(this) - DataType.INTEGER -> IntegerType(this) - DataType.DATE -> DateType(this) - DataType.DATETIME -> DateTimeType(this) - DataType.TIME -> TimeType(this) - DataType.STRING -> StringType(this) - DataType.URI -> UriType(this) - DataType.CODING -> this.tryDecodeJson() - DataType.QUANTITY -> this.tryDecodeJson() - else -> null // TODO cast the (several) remaining Enumeration.DataTypes - } -} diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/util/FhirDataTypesUtilKtTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/util/FhirDataTypesUtilKtTest.kt new file mode 100644 index 0000000000..20a263df2d --- /dev/null +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/util/FhirDataTypesUtilKtTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2021-2024 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.util + +import java.io.IOException +import java.math.BigDecimal +import org.hl7.fhir.r4.model.BooleanType +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.DateTimeType +import org.hl7.fhir.r4.model.DateType +import org.hl7.fhir.r4.model.DecimalType +import org.hl7.fhir.r4.model.Enumerations.DataType +import org.hl7.fhir.r4.model.IntegerType +import org.hl7.fhir.r4.model.Quantity +import org.hl7.fhir.r4.model.Reference +import org.hl7.fhir.r4.model.StringType +import org.hl7.fhir.r4.model.TimeType +import org.hl7.fhir.r4.model.UriType +import org.junit.Assert +import org.junit.Test +import org.smartregister.fhircore.engine.util.extension.valueToString + +class FhirDataTypesUtilKtTest { + @Test + fun testCastToTypeReturnsCorrectTypes() { + val booleanType = "true".castToType(DataType.BOOLEAN) + Assert.assertEquals(BooleanType().fhirType(), booleanType?.fhirType()) + Assert.assertEquals("true", booleanType.valueToString()) + + val decimalType = "6.4".castToType(DataType.DECIMAL) + Assert.assertEquals(DecimalType().fhirType(), decimalType?.fhirType()) + Assert.assertEquals("6.4", decimalType.valueToString()) + + val integerType = "4".castToType(DataType.INTEGER) + Assert.assertEquals(IntegerType().fhirType(), integerType?.fhirType()) + Assert.assertEquals("4", integerType.valueToString()) + + val dateType = "2020-02-02".castToType(DataType.DATE) + Assert.assertEquals(DateType().fhirType(), dateType?.fhirType()) + Assert.assertEquals("02-Feb-2020", dateType.valueToString()) + + val dateTimeType = "2020-02-02T13:00:32".castToType(DataType.DATETIME) + Assert.assertEquals(DateTimeType().fhirType(), dateTimeType?.fhirType()) + Assert.assertEquals("02-Feb-2020", dateTimeType.valueToString()) + + val timeType = "T13:00:32".castToType(DataType.TIME) + Assert.assertEquals(TimeType().fhirType(), timeType?.fhirType()) + Assert.assertEquals("T13:00:32", timeType.valueToString()) + + val stringType = "str".castToType(DataType.STRING) + Assert.assertEquals(StringType().fhirType(), stringType?.fhirType()) + Assert.assertEquals("str", stringType.valueToString()) + + val uriType = "https://str.org".castToType(DataType.URI) + Assert.assertEquals(UriType().fhirType(), uriType?.fhirType()) + Assert.assertEquals("https://str.org", uriType.valueToString()) + + val codingType = "{ \"code\": \"alright\" }".castToType(DataType.CODING) + Assert.assertTrue(codingType is Coding) + codingType as Coding + Assert.assertEquals("alright", codingType.code) + + Assert.assertThrows(IOException::class.java) { + val codingType = "invalid".castToType(DataType.CODING) + Assert.assertEquals(null, codingType) + } + + val quantityType = " { \"value\": 42 }".castToType(DataType.QUANTITY) + Assert.assertTrue(quantityType is Quantity) + quantityType as Quantity + Assert.assertEquals(BigDecimal(42), quantityType.value) + + Assert.assertThrows(IOException::class.java) { + val quantityType = "invalid".castToType(DataType.QUANTITY) + Assert.assertEquals(null, quantityType) + } + + val referenceType = "{ \"reference\": \"Patient/0\"}".castToType(DataType.REFERENCE) + Assert.assertTrue(referenceType is Reference) + referenceType as Reference + Assert.assertEquals("Patient/0", referenceType.reference) + } +} diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtensionTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtensionTest.kt index 358852ec25..5e55a48403 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtensionTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/util/extension/QuestionnaireExtensionTest.kt @@ -18,12 +18,8 @@ package org.smartregister.fhircore.engine.util.extension import java.util.UUID import kotlinx.coroutines.test.runTest -import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.Coding -import org.hl7.fhir.r4.model.DateTimeType -import org.hl7.fhir.r4.model.DateType -import org.hl7.fhir.r4.model.DecimalType import org.hl7.fhir.r4.model.Enumerations import org.hl7.fhir.r4.model.Enumerations.DataType import org.hl7.fhir.r4.model.Expression @@ -33,9 +29,7 @@ import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.ResourceType import org.hl7.fhir.r4.model.StringType -import org.hl7.fhir.r4.model.TimeType import org.hl7.fhir.r4.model.Type -import org.hl7.fhir.r4.model.UriType import org.junit.Assert import org.junit.Before import org.junit.Test @@ -408,50 +402,6 @@ class QuestionnaireExtensionTest : RobolectricTest() { Assert.assertTrue(questionnaireItemComponent.hasExtension(EXTENSION_INITIAL_EXPRESSION_URL)) } - @Test - fun testCastToTypeReturnsCorrectTypes() { - val booleanType = "true".castToType(DataType.BOOLEAN) - Assert.assertEquals(BooleanType().fhirType(), booleanType?.fhirType()) - Assert.assertEquals("true", booleanType.valueToString()) - - val decimalType = "6.4".castToType(DataType.DECIMAL) - Assert.assertEquals(DecimalType().fhirType(), decimalType?.fhirType()) - Assert.assertEquals("6.4", decimalType.valueToString()) - - val integerType = "4".castToType(DataType.INTEGER) - Assert.assertEquals(IntegerType().fhirType(), integerType?.fhirType()) - Assert.assertEquals("4", integerType.valueToString()) - - val dateType = "2020-02-02".castToType(DataType.DATE) - Assert.assertEquals(DateType().fhirType(), dateType?.fhirType()) - Assert.assertEquals("02-Feb-2020", dateType.valueToString()) - - val dateTimeType = "2020-02-02T13:00:32".castToType(DataType.DATETIME) - Assert.assertEquals(DateTimeType().fhirType(), dateTimeType?.fhirType()) - Assert.assertEquals("02-Feb-2020", dateTimeType.valueToString()) - - val timeType = "T13:00:32".castToType(DataType.TIME) - Assert.assertEquals(TimeType().fhirType(), timeType?.fhirType()) - Assert.assertEquals("T13:00:32", timeType.valueToString()) - - val stringType = "str".castToType(DataType.STRING) - Assert.assertEquals(StringType().fhirType(), stringType?.fhirType()) - Assert.assertEquals("str", stringType.valueToString()) - - val uriType = "https://str.org".castToType(DataType.URI) - Assert.assertEquals(UriType().fhirType(), uriType?.fhirType()) - Assert.assertEquals("https://str.org", uriType.valueToString()) - - // test invalid JSON - val codingType = "invalid".castToType(DataType.CODING) - Assert.assertEquals(null, codingType) - - val quantityType = "invalid".castToType(DataType.QUANTITY) - Assert.assertEquals(null, quantityType) - - // TODO: test valid JSON - } - @Test fun testPrepopulateQuestionnaireWithComputedValues() = runTest { val questionnaireConfig =