diff --git a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/BaseFhirTypeConverter.java b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/BaseFhirTypeConverter.java index 4002d9d2b..abc3a1828 100644 --- a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/BaseFhirTypeConverter.java +++ b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/BaseFhirTypeConverter.java @@ -42,7 +42,7 @@ public boolean isFhirType(Object value) { } @Override - public Iterable toFhirTypes(Iterable values) { + public List toFhirTypes(Iterable values) { List converted = new ArrayList<>(); for (Object value : values) { if (value == null) { diff --git a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2FhirTypeConverter.java b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2FhirTypeConverter.java index 1b1846617..b5813fbea 100644 --- a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2FhirTypeConverter.java +++ b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2FhirTypeConverter.java @@ -221,13 +221,86 @@ public ICompositeType toFhirRange(Interval value) { return range; } + private static BooleanType emptyBooleanWithExtension(String url, Type value) { + var result = new BooleanType((String) null); + result.addExtension().setUrl(url).setValue(value); + return result; + } + + private static void addPartWithNameAndValue( + Parameters.ParametersParameterComponent param, String key, Object value) { + if (value instanceof Parameters.ParametersParameterComponent) { + var part = (Parameters.ParametersParameterComponent) value; + part.setName(key); + param.addPart(part); + } else { + var part = param.addPart().setName(key); + if (value instanceof Resource) { + part.setResource((Resource) value); + } else if (value instanceof Type) { + part.setValue((Type) value); + } else { + throw new IllegalArgumentException( + "Unsupported FHIR type: " + value.getClass().getName()); + } + } + } + + private static Iterable asIterable(Object value) { + if (value instanceof Iterable) { + return (Iterable) value; + } else { + return null; + } + } + + private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) { + if (value == null) { + // Null value, add a single empty value with an extension indicating the reason + var dataAbsentValue = emptyBooleanWithExtension( + DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE)); + addPartWithNameAndValue(param, key, dataAbsentValue); + return; + } + + var iterable = asIterable(value); + if (iterable == null) { + // Single, non-null value + addPartWithNameAndValue(param, key, toFhirType(value)); + return; + } + + if (!iterable.iterator().hasNext()) { + // Empty list + var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true)); + addPartWithNameAndValue(param, key, emptyListValue); + } else { + // Non-empty list, one part per value + var fhirTypes = this.toFhirTypes(iterable); + for (var fhirType : fhirTypes) { + addPartWithNameAndValue(param, key, fhirType); + } + } + } + @Override public IBase toFhirTuple(Tuple value) { if (value == null) { return null; } - throw new NotImplementedException("can't convert Tuples"); + var parameters = new Parameters(); + var param = parameters.addParameter(); + + if (value.getElements().isEmpty()) { + param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true))); + } + + for (String key : value.getElements().keySet()) { + addElementToParameter(param, key, value.getElements().get(key)); + } + + return param; } @Override diff --git a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3FhirTypeConverter.java b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3FhirTypeConverter.java index c24dee528..d58927482 100644 --- a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3FhirTypeConverter.java +++ b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3FhirTypeConverter.java @@ -220,13 +220,86 @@ public ICompositeType toFhirRange(Interval value) { return range; } + private static BooleanType emptyBooleanWithExtension(String url, Type value) { + var result = new BooleanType((String) null); + result.addExtension().setUrl(url).setValue(value); + return result; + } + + private static void addPartWithNameAndValue( + Parameters.ParametersParameterComponent param, String key, Object value) { + if (value instanceof Parameters.ParametersParameterComponent) { + var part = (Parameters.ParametersParameterComponent) value; + part.setName(key); + param.addPart(part); + } else { + var part = param.addPart().setName(key); + if (value instanceof Resource) { + part.setResource((Resource) value); + } else if (value instanceof Type) { + part.setValue((Type) value); + } else { + throw new IllegalArgumentException( + "Unsupported FHIR type: " + value.getClass().getName()); + } + } + } + + private static Iterable asIterable(Object value) { + if (value instanceof Iterable) { + return (Iterable) value; + } else { + return null; + } + } + + private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) { + if (value == null) { + // Null value, add a single empty value with an extension indicating the reason + var dataAbsentValue = emptyBooleanWithExtension( + DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE)); + addPartWithNameAndValue(param, key, dataAbsentValue); + return; + } + + var iterable = asIterable(value); + if (iterable == null) { + // Single, non-null value + addPartWithNameAndValue(param, key, toFhirType(value)); + return; + } + + if (!iterable.iterator().hasNext()) { + // Empty list + var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true)); + addPartWithNameAndValue(param, key, emptyListValue); + } else { + // Non-empty list, one part per value + var fhirTypes = this.toFhirTypes(iterable); + for (var fhirType : fhirTypes) { + addPartWithNameAndValue(param, key, fhirType); + } + } + } + @Override public IBase toFhirTuple(Tuple value) { if (value == null) { return null; } - throw new NotImplementedException("can't convert Tuples"); + var parameters = new Parameters(); + var param = parameters.addParameter(); + + if (value.getElements().isEmpty()) { + param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true))); + } + + for (String key : value.getElements().keySet()) { + addElementToParameter(param, key, value.getElements().get(key)); + } + + return param; } @Override diff --git a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/FhirTypeConverter.java b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/FhirTypeConverter.java index 835479e1c..c81c3e48f 100644 --- a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/FhirTypeConverter.java +++ b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/FhirTypeConverter.java @@ -1,6 +1,7 @@ package org.opencds.cqf.cql.engine.fhir.converter; import java.math.BigDecimal; +import java.util.List; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.ICompositeType; @@ -28,6 +29,12 @@ */ public interface FhirTypeConverter { + static final String EMPTY_LIST_EXT_URL = "http://hl7.org/fhir/StructureDefinition/cqf-isEmptyList"; + static final String EMPTY_TUPLE_EXT_URL = "http://hl7.org/fhir/StructureDefinition/cqf-isEmptyTuple"; + static final String DATA_ABSENT_REASON_EXT_URL = "http://hl7.org/fhir/StructureDefinition/data-absent-reason"; + static final String DATA_ABSENT_REASON_UNKNOWN_CODE = "unknown"; + static final String CQL_TYPE_EXT_URL = "http://hl7.org/fhir/StructureDefinition/cqf-cqlType"; + // CQL-to-FHIR conversions /** @@ -53,9 +60,9 @@ public interface FhirTypeConverter { * nulls, and sublist hierarchy * * @param values an Iterable containing CQL structures, nulls, or sublists - * @return an Iterable containing FHIR types, nulls, and sublists + * @return an List containing FHIR types, nulls, and sublists */ - public Iterable toFhirTypes(Iterable values); + public List toFhirTypes(Iterable values); /** * Converts a String to a FHIR Id diff --git a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R4FhirTypeConverter.java b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R4FhirTypeConverter.java index c24ef73b0..b69f1f8d6 100644 --- a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R4FhirTypeConverter.java +++ b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R4FhirTypeConverter.java @@ -179,7 +179,8 @@ public ICompositeType toFhirPeriod(Interval value) { return period; } else if (getSimpleName(value.getPointType().getTypeName()).equals("Date")) { - // TODO: This will construct DateTimeType values in FHIR with the system timezone id, not the + // TODO: This will construct DateTimeType values in FHIR with the system + // timezone id, not the // timezoneoffset of the evaluation request..... this is a bug waiting to happen if (value.getStart() != null) { period.setStart(toFhirDate((Date) value.getStart()).getValue()); @@ -212,13 +213,86 @@ public ICompositeType toFhirRange(Interval value) { return range; } + private static BooleanType emptyBooleanWithExtension(String url, Type value) { + var result = new BooleanType((String) null); + result.addExtension().setUrl(url).setValue(value); + return result; + } + + private static void addPartWithNameAndValue( + Parameters.ParametersParameterComponent param, String key, Object value) { + if (value instanceof Parameters.ParametersParameterComponent) { + var part = (Parameters.ParametersParameterComponent) value; + part.setName(key); + param.addPart(part); + } else { + var part = param.addPart().setName(key); + if (value instanceof Resource) { + part.setResource((Resource) value); + } else if (value instanceof Type) { + part.setValue((Type) value); + } else { + throw new IllegalArgumentException( + "Unsupported FHIR type: " + value.getClass().getName()); + } + } + } + + private static Iterable asIterable(Object value) { + if (value instanceof Iterable) { + return (Iterable) value; + } else { + return null; + } + } + + private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) { + if (value == null) { + // Null value, add a single empty value with an extension indicating the reason + var dataAbsentValue = emptyBooleanWithExtension( + DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE)); + addPartWithNameAndValue(param, key, dataAbsentValue); + return; + } + + var iterable = asIterable(value); + if (iterable == null) { + // Single, non-null value + addPartWithNameAndValue(param, key, toFhirType(value)); + return; + } + + if (!iterable.iterator().hasNext()) { + // Empty list + var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true)); + addPartWithNameAndValue(param, key, emptyListValue); + } else { + // Non-empty list, one part per value + var fhirTypes = this.toFhirTypes(iterable); + for (var fhirType : fhirTypes) { + addPartWithNameAndValue(param, key, fhirType); + } + } + } + @Override public IBase toFhirTuple(Tuple value) { if (value == null) { return null; } - throw new NotImplementedException("can't convert Tuples"); + var parameters = new Parameters(); + var param = parameters.addParameter(); + + if (value.getElements().isEmpty()) { + param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true))); + } + + for (String key : value.getElements().keySet()) { + addElementToParameter(param, key, value.getElements().get(key)); + } + + return param; } @Override diff --git a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R5FhirTypeConverter.java b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R5FhirTypeConverter.java index c53b694e2..c8c53194e 100644 --- a/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R5FhirTypeConverter.java +++ b/Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/R5FhirTypeConverter.java @@ -212,13 +212,86 @@ public ICompositeType toFhirRange(Interval value) { return range; } + private static BooleanType emptyBooleanWithExtension(String url, DataType value) { + var result = new BooleanType((String) null); + result.addExtension().setUrl(url).setValue(value); + return result; + } + + private static void addPartWithNameAndValue( + Parameters.ParametersParameterComponent param, String key, Object value) { + if (value instanceof Parameters.ParametersParameterComponent) { + var part = (Parameters.ParametersParameterComponent) value; + part.setName(key); + param.addPart(part); + } else { + var part = param.addPart().setName(key); + if (value instanceof Resource) { + part.setResource((Resource) value); + } else if (value instanceof DataType) { + part.setValue((DataType) value); + } else { + throw new IllegalArgumentException( + "Unsupported FHIR type: " + value.getClass().getName()); + } + } + } + + private static Iterable asIterable(Object value) { + if (value instanceof Iterable) { + return (Iterable) value; + } else { + return null; + } + } + + private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) { + if (value == null) { + // Null value, add a single empty value with an extension indicating the reason + var dataAbsentValue = emptyBooleanWithExtension( + DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE)); + addPartWithNameAndValue(param, key, dataAbsentValue); + return; + } + + var iterable = asIterable(value); + if (iterable == null) { + // Single, non-null value + addPartWithNameAndValue(param, key, toFhirType(value)); + return; + } + + if (!iterable.iterator().hasNext()) { + // Empty list + var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true)); + addPartWithNameAndValue(param, key, emptyListValue); + } else { + // Non-empty list, one part per value + var fhirTypes = this.toFhirTypes(iterable); + for (var fhirType : fhirTypes) { + addPartWithNameAndValue(param, key, fhirType); + } + } + } + @Override public IBase toFhirTuple(Tuple value) { if (value == null) { return null; } - throw new NotImplementedException("can't convert Tuples"); + var parameters = new Parameters(); + var param = parameters.addParameter(); + + if (value.getElements().isEmpty()) { + param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true))); + } + + for (String key : value.getElements().keySet()) { + addElementToParameter(param, key, value.getElements().get(key)); + } + + return param; } @Override diff --git a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2TypeConverterTests.java b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2TypeConverterTests.java index 44c468525..e35315c88 100644 --- a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2TypeConverterTests.java +++ b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu2TypeConverterTests.java @@ -18,6 +18,7 @@ import java.util.Iterator; import java.util.List; import java.util.TimeZone; +import java.util.stream.Collectors; import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.dstu2.model.Attachment; import org.hl7.fhir.dstu2.model.Base; @@ -27,9 +28,12 @@ import org.hl7.fhir.dstu2.model.DateTimeType; import org.hl7.fhir.dstu2.model.DateType; import org.hl7.fhir.dstu2.model.DecimalType; +import org.hl7.fhir.dstu2.model.Encounter; import org.hl7.fhir.dstu2.model.IdType; import org.hl7.fhir.dstu2.model.InstantType; import org.hl7.fhir.dstu2.model.IntegerType; +import org.hl7.fhir.dstu2.model.Parameters; +import org.hl7.fhir.dstu2.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.dstu2.model.Patient; import org.hl7.fhir.dstu2.model.Period; import org.hl7.fhir.dstu2.model.Range; @@ -40,6 +44,7 @@ import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -67,7 +72,7 @@ protected Boolean compareIterables(Iterable left, Iterable right return !leftIterator.hasNext() && !rightIterator.hasNext(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "null"}) protected Boolean compareObjects(Object left, Object right) { if (left == null ^ right == null) { return false; @@ -522,15 +527,102 @@ void invalidIntervalToFhirInterval() { }); } + private static List getPartsByName(ParametersParameterComponent ppc, String name) { + return ppc.getPart().stream().filter(p -> p.getName().equals(name)).collect(Collectors.toList()); + } + @Test void tupleToFhirTuple() { - IBase expected = typeConverter.toFhirTuple(null); - assertNull(expected); + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(null); + assertNull(actual); var tuple = new Tuple(); - assertThrows(NotImplementedException.class, () -> { - typeConverter.toFhirTuple(tuple); - }); + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + Assertions.assertNotNull(actual); + assertEquals( + FhirTypeConverter.EMPTY_TUPLE_EXT_URL, + actual.getValue().getExtension().get(0).getUrl()); + + var ints = new ArrayList(); + for (int i = 0; i < 5; i++) { + ints.add(i); + } + + tuple.getElements().put("V", ints); + tuple.getElements().put("W", null); + tuple.getElements().put("X", 5); + tuple.getElements().put("Y", new Encounter().setId("123")); + tuple.getElements().put("Z", new ArrayList<>()); + + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + var first = actual; + assertEquals(9, first.getPart().size()); + + var v = getPartsByName(first, "V"); + assertEquals(5, v.size()); + assertEquals(0, ((IntegerType) v.get(0).getValue()).getValue()); + + var w = getPartsByName(first, "W").get(0); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + w.getValue().getExtension().get(0).getUrl()); + + var x = getPartsByName(first, "X").get(0); + assertEquals(5, ((IntegerType) x.getValue()).getValue()); + + var y = getPartsByName(first, "Y").get(0); + assertEquals("123", y.getResource().getId()); + + var z = getPartsByName(first, "Z").get(0); + assertEquals( + FhirTypeConverter.EMPTY_LIST_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + } + + @Test + void complexTupleToFhirTuple() { + var innerTuple = new Tuple(); + innerTuple.getElements().put("X", 1); + innerTuple.getElements().put("Y", 2); + innerTuple.getElements().put("Z", null); + var outerTuple = new Tuple(); + outerTuple.getElements().put("A", innerTuple); + var tupleList = new ArrayList(); + for (int i = 0; i < 3; i++) { + var elementTuple = new Tuple(); + elementTuple.getElements().put("P", i); + elementTuple.getElements().put("Q", i + 1); + tupleList.add(elementTuple); + } + outerTuple.getElements().put("B", tupleList); + + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(outerTuple); + var first = actual; + assertEquals(4, first.getPart().size()); + + var a = getPartsByName(first, "A"); + assertEquals(1, a.size()); + assertEquals(3, a.get(0).getPart().size()); + var x = a.get(0).getPart().get(0); + assertEquals(1, ((IntegerType) x.getValue()).getValue()); + var y = a.get(0).getPart().get(1); + assertEquals(2, ((IntegerType) y.getValue()).getValue()); + var z = a.get(0).getPart().get(2); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + + var b = getPartsByName(first, "B"); + assertEquals(3, b.size()); + var b1 = b.get(0); + var p = getPartsByName(b1, "P"); + assertEquals(1, p.size()); + assertEquals(0, ((IntegerType) p.get(0).getValue()).getValue()); + var q = getPartsByName(b1, "Q"); + assertEquals(1, q.size()); + assertEquals(1, ((IntegerType) q.get(0).getValue()).getValue()); } // FHIR-to-CQL diff --git a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3TypeConverterTests.java b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3TypeConverterTests.java index 61cdc6519..38a75bce3 100644 --- a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3TypeConverterTests.java +++ b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/Dstu3TypeConverterTests.java @@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -18,6 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.TimeZone; +import java.util.stream.Collectors; import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.Base; @@ -27,9 +29,12 @@ import org.hl7.fhir.dstu3.model.DateTimeType; import org.hl7.fhir.dstu3.model.DateType; import org.hl7.fhir.dstu3.model.DecimalType; +import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.InstantType; import org.hl7.fhir.dstu3.model.IntegerType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.Range; @@ -67,7 +72,7 @@ protected Boolean compareIterables(Iterable left, Iterable right return !leftIterator.hasNext() && !rightIterator.hasNext(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "null"}) protected Boolean compareObjects(Object left, Object right) { if (left == null ^ right == null) { return false; @@ -526,15 +531,102 @@ void invalidIntervalToFhirInterval() { }); } + private static List getPartsByName(ParametersParameterComponent ppc, String name) { + return ppc.getPart().stream().filter(p -> p.getName().equals(name)).collect(Collectors.toList()); + } + @Test void tupleToFhirTuple() { - IBase expected = typeConverter.toFhirTuple(null); - assertNull(expected); + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(null); + assertNull(actual); var tuple = new Tuple(); - assertThrows(NotImplementedException.class, () -> { - typeConverter.toFhirTuple(tuple); - }); + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + assertNotNull(actual); + assertEquals( + FhirTypeConverter.EMPTY_TUPLE_EXT_URL, + actual.getValue().getExtension().get(0).getUrl()); + + var ints = new ArrayList(); + for (int i = 0; i < 5; i++) { + ints.add(i); + } + + tuple.getElements().put("V", ints); + tuple.getElements().put("W", null); + tuple.getElements().put("X", 5); + tuple.getElements().put("Y", new Encounter().setId("123")); + tuple.getElements().put("Z", new ArrayList<>()); + + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + var first = actual; + assertEquals(9, first.getPart().size()); + + var v = getPartsByName(first, "V"); + assertEquals(5, v.size()); + assertEquals(0, ((IntegerType) v.get(0).getValue()).getValue()); + + var w = getPartsByName(first, "W").get(0); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + w.getValue().getExtension().get(0).getUrl()); + + var x = getPartsByName(first, "X").get(0); + assertEquals(5, ((IntegerType) x.getValue()).getValue()); + + var y = getPartsByName(first, "Y").get(0); + assertEquals("123", y.getResource().getId()); + + var z = getPartsByName(first, "Z").get(0); + assertEquals( + FhirTypeConverter.EMPTY_LIST_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + } + + @Test + void complexTupleToFhirTuple() { + var innerTuple = new Tuple(); + innerTuple.getElements().put("X", 1); + innerTuple.getElements().put("Y", 2); + innerTuple.getElements().put("Z", null); + var outerTuple = new Tuple(); + outerTuple.getElements().put("A", innerTuple); + var tupleList = new ArrayList(); + for (int i = 0; i < 3; i++) { + var elementTuple = new Tuple(); + elementTuple.getElements().put("P", i); + elementTuple.getElements().put("Q", i + 1); + tupleList.add(elementTuple); + } + outerTuple.getElements().put("B", tupleList); + + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(outerTuple); + var first = actual; + assertEquals(4, first.getPart().size()); + + var a = getPartsByName(first, "A"); + assertEquals(1, a.size()); + assertEquals(3, a.get(0).getPart().size()); + var x = a.get(0).getPart().get(0); + assertEquals(1, ((IntegerType) x.getValue()).getValue()); + var y = a.get(0).getPart().get(1); + assertEquals(2, ((IntegerType) y.getValue()).getValue()); + var z = a.get(0).getPart().get(2); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + + var b = getPartsByName(first, "B"); + assertEquals(3, b.size()); + var b1 = b.get(0); + var p = getPartsByName(b1, "P"); + assertEquals(1, p.size()); + assertEquals(0, ((IntegerType) p.get(0).getValue()).getValue()); + var q = getPartsByName(b1, "Q"); + assertEquals(1, q.size()); + assertEquals(1, ((IntegerType) q.get(0).getValue()).getValue()); } // FHIR-to-CQL diff --git a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R4TypeConverterTests.java b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R4TypeConverterTests.java index a040bddf7..0e446722c 100644 --- a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R4TypeConverterTests.java +++ b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R4TypeConverterTests.java @@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -18,6 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.TimeZone; +import java.util.stream.Collectors; import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.ICompositeType; @@ -31,9 +33,12 @@ 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.Encounter; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Period; import org.hl7.fhir.r4.model.Range; @@ -67,7 +72,7 @@ protected Boolean compareIterables(Iterable left, Iterable right return !leftIterator.hasNext() && !rightIterator.hasNext(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "null"}) protected Boolean compareObjects(Object left, Object right) { if (left == null ^ right == null) { return false; @@ -526,15 +531,102 @@ void invalidIntervalToFhirInterval() { }); } + private static List getPartsByName(ParametersParameterComponent ppc, String name) { + return ppc.getPart().stream().filter(p -> p.getName().equals(name)).collect(Collectors.toList()); + } + @Test void tupleToFhirTuple() { - IBase expected = typeConverter.toFhirTuple(null); - assertNull(expected); + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(null); + assertNull(actual); var tuple = new Tuple(); - assertThrows(NotImplementedException.class, () -> { - typeConverter.toFhirTuple(tuple); - }); + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + assertNotNull(actual); + assertEquals( + FhirTypeConverter.EMPTY_TUPLE_EXT_URL, + actual.getValue().getExtension().get(0).getUrl()); + + var ints = new ArrayList(); + for (int i = 0; i < 5; i++) { + ints.add(i); + } + + tuple.getElements().put("V", ints); + tuple.getElements().put("W", null); + tuple.getElements().put("X", 5); + tuple.getElements().put("Y", new Encounter().setId("123")); + tuple.getElements().put("Z", new ArrayList<>()); + + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + var first = actual; + assertEquals(9, first.getPart().size()); + + var v = getPartsByName(first, "V"); + assertEquals(5, v.size()); + assertEquals(0, ((IntegerType) v.get(0).getValue()).getValue()); + + var w = getPartsByName(first, "W").get(0); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + w.getValue().getExtension().get(0).getUrl()); + + var x = getPartsByName(first, "X").get(0); + assertEquals(5, ((IntegerType) x.getValue()).getValue()); + + var y = getPartsByName(first, "Y").get(0); + assertEquals("123", y.getResource().getId()); + + var z = getPartsByName(first, "Z").get(0); + assertEquals( + FhirTypeConverter.EMPTY_LIST_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + } + + @Test + void complexTupleToFhirTuple() { + var innerTuple = new Tuple(); + innerTuple.getElements().put("X", 1); + innerTuple.getElements().put("Y", 2); + innerTuple.getElements().put("Z", null); + var outerTuple = new Tuple(); + outerTuple.getElements().put("A", innerTuple); + var tupleList = new ArrayList(); + for (int i = 0; i < 3; i++) { + var elementTuple = new Tuple(); + elementTuple.getElements().put("P", i); + elementTuple.getElements().put("Q", i + 1); + tupleList.add(elementTuple); + } + outerTuple.getElements().put("B", tupleList); + + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(outerTuple); + var first = actual; + assertEquals(4, first.getPart().size()); + + var a = getPartsByName(first, "A"); + assertEquals(1, a.size()); + assertEquals(3, a.get(0).getPart().size()); + var x = a.get(0).getPart().get(0); + assertEquals(1, ((IntegerType) x.getValue()).getValue()); + var y = a.get(0).getPart().get(1); + assertEquals(2, ((IntegerType) y.getValue()).getValue()); + var z = a.get(0).getPart().get(2); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + + var b = getPartsByName(first, "B"); + assertEquals(3, b.size()); + var b1 = b.get(0); + var p = getPartsByName(b1, "P"); + assertEquals(1, p.size()); + assertEquals(0, ((IntegerType) p.get(0).getValue()).getValue()); + var q = getPartsByName(b1, "Q"); + assertEquals(1, q.size()); + assertEquals(1, ((IntegerType) q.get(0).getValue()).getValue()); } // FHIR-to-CQL diff --git a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R5TypeConverterTests.java b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R5TypeConverterTests.java index ac055c092..b80bfb695 100644 --- a/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R5TypeConverterTests.java +++ b/Src/java/engine-fhir/src/test/java/org/opencds/cqf/cql/engine/fhir/converter/R5TypeConverterTests.java @@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -18,6 +19,7 @@ import java.util.Iterator; import java.util.List; import java.util.TimeZone; +import java.util.stream.Collectors; import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.ICompositeType; @@ -31,9 +33,12 @@ import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.DateType; import org.hl7.fhir.r5.model.DecimalType; +import org.hl7.fhir.r5.model.Encounter; import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.InstantType; import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Period; import org.hl7.fhir.r5.model.Range; @@ -67,7 +72,7 @@ protected Boolean compareIterables(Iterable left, Iterable right return !leftIterator.hasNext() && !rightIterator.hasNext(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "null"}) protected Boolean compareObjects(Object left, Object right) { if (left == null ^ right == null) { return false; @@ -526,15 +531,102 @@ void invalidIntervalToFhirInterval() { }); } + private static List getPartsByName(ParametersParameterComponent ppc, String name) { + return ppc.getPart().stream().filter(p -> p.getName().equals(name)).collect(Collectors.toList()); + } + @Test void tupleToFhirTuple() { - IBase expected = typeConverter.toFhirTuple(null); - assertNull(expected); + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(null); + assertNull(actual); var tuple = new Tuple(); - assertThrows(NotImplementedException.class, () -> { - typeConverter.toFhirTuple(tuple); - }); + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + assertNotNull(actual); + assertEquals( + FhirTypeConverter.EMPTY_TUPLE_EXT_URL, + actual.getValue().getExtension().get(0).getUrl()); + + var ints = new ArrayList(); + for (int i = 0; i < 5; i++) { + ints.add(i); + } + + tuple.getElements().put("V", ints); + tuple.getElements().put("W", null); + tuple.getElements().put("X", 5); + tuple.getElements().put("Y", new Encounter().setId("123")); + tuple.getElements().put("Z", new ArrayList<>()); + + actual = (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(tuple); + var first = actual; + assertEquals(9, first.getPart().size()); + + var v = getPartsByName(first, "V"); + assertEquals(5, v.size()); + assertEquals(0, ((IntegerType) v.get(0).getValue()).getValue()); + + var w = getPartsByName(first, "W").get(0); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + w.getValue().getExtension().get(0).getUrl()); + + var x = getPartsByName(first, "X").get(0); + assertEquals(5, ((IntegerType) x.getValue()).getValue()); + + var y = getPartsByName(first, "Y").get(0); + assertEquals("123", y.getResource().getId()); + + var z = getPartsByName(first, "Z").get(0); + assertEquals( + FhirTypeConverter.EMPTY_LIST_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + } + + @Test + void complexTupleToFhirTuple() { + var innerTuple = new Tuple(); + innerTuple.getElements().put("X", 1); + innerTuple.getElements().put("Y", 2); + innerTuple.getElements().put("Z", null); + var outerTuple = new Tuple(); + outerTuple.getElements().put("A", innerTuple); + var tupleList = new ArrayList(); + for (int i = 0; i < 3; i++) { + var elementTuple = new Tuple(); + elementTuple.getElements().put("P", i); + elementTuple.getElements().put("Q", i + 1); + tupleList.add(elementTuple); + } + outerTuple.getElements().put("B", tupleList); + + Parameters.ParametersParameterComponent actual = + (Parameters.ParametersParameterComponent) typeConverter.toFhirTuple(outerTuple); + var first = actual; + assertEquals(4, first.getPart().size()); + + var a = getPartsByName(first, "A"); + assertEquals(1, a.size()); + assertEquals(3, a.get(0).getPart().size()); + var x = a.get(0).getPart().get(0); + assertEquals(1, ((IntegerType) x.getValue()).getValue()); + var y = a.get(0).getPart().get(1); + assertEquals(2, ((IntegerType) y.getValue()).getValue()); + var z = a.get(0).getPart().get(2); + assertEquals( + FhirTypeConverter.DATA_ABSENT_REASON_EXT_URL, + z.getValue().getExtension().get(0).getUrl()); + + var b = getPartsByName(first, "B"); + assertEquals(3, b.size()); + var b1 = b.get(0); + var p = getPartsByName(b1, "P"); + assertEquals(1, p.size()); + assertEquals(0, ((IntegerType) p.get(0).getValue()).getValue()); + var q = getPartsByName(b1, "Q"); + assertEquals(1, q.size()); + assertEquals(1, ((IntegerType) q.get(0).getValue()).getValue()); } // FHIR-to-CQL