Return lists for FHIRPath constants in r4 (#902)

* Test for r5

* Test for r4b

* r4 test + fix

Co-authored-by: dotasek <david.otasek@smilecdr.com>
This commit is contained in:
dotasek 2022-08-25 10:02:13 -04:00 committed by GitHub
parent 59a9700181
commit e79b2bf199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 97 deletions

View File

@ -199,7 +199,7 @@ public class FHIRPathEngine {
* @param beforeContext - whether this is being called before the name is resolved locally, or not
* @return the value of the reference (or null, if it's not valid, though can throw an exception if desired)
*/
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException;
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException;
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException;
/**
@ -1162,9 +1162,9 @@ public class FHIRPathEngine {
work.addAll(work2);
break;
case Constant:
Base b = resolveConstant(context, exp.getConstant(), false, exp);
List<Base> b = resolveConstant(context, exp.getConstant(), false, exp);
if (b != null)
work.add(b);
work.addAll(b);
break;
case Group:
work2 = execute(context, focus, exp.getGroup(), atEntry);
@ -1292,14 +1292,14 @@ public class FHIRPathEngine {
return result;
}
private Base resolveConstant(ExecutionContext context, Base constant, boolean beforeContext, ExpressionNode expr) throws PathEngineException {
private List<Base> resolveConstant(ExecutionContext context, Base constant, boolean beforeContext, ExpressionNode expr) throws PathEngineException {
if (!(constant instanceof FHIRConstant))
return constant;
return new ArrayList<>(Arrays.asList(constant));
FHIRConstant c = (FHIRConstant) constant;
if (c.getValue().startsWith("%")) {
return resolveConstant(context, c.getValue(), beforeContext, expr);
} else if (c.getValue().startsWith("@")) {
return processDateConstant(context.appInfo, c.getValue().substring(1));
return new ArrayList<>(Arrays.asList(processDateConstant(context.appInfo, c.getValue().substring(1))));
} else
throw new PathEngineException("Invaild FHIR Constant "+c.getValue(), expr.getStart(), expr.toString());
}
@ -1323,31 +1323,31 @@ public class FHIRPathEngine {
}
private Base resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr) throws PathEngineException {
private List<Base> resolveConstant(ExecutionContext context, String s, boolean beforeContext, ExpressionNode expr) throws PathEngineException {
if (s.equals("%sct"))
return new StringType("http://snomed.info/sct").noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("http://snomed.info/sct").noExtensions()));
else if (s.equals("%loinc"))
return new StringType("http://loinc.org").noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("http://loinc.org").noExtensions()));
else if (s.equals("%ucum"))
return new StringType("http://unitsofmeasure.org").noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("http://unitsofmeasure.org").noExtensions()));
else if (s.equals("%resource")) {
if (context.focusResource == null)
throw new PathEngineException("Cannot use %resource in this context", expr.getStart(), expr.toString());
return context.focusResource;
return new ArrayList<>(Arrays.asList(context.focusResource));
} else if (s.equals("%rootResource")) {
if (context.rootResource == null)
throw new PathEngineException("Cannot use %rootResource in this context", expr.getStart(), expr.toString());
return context.rootResource;
return new ArrayList<>(Arrays.asList(context.rootResource));
} else if (s.equals("%context")) {
return context.context;
return new ArrayList<>(Arrays.asList(context.context));
} else if (s.equals("%us-zip"))
return new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("[0-9]{5}(-[0-9]{4}){0,1}").noExtensions()));
else if (s.startsWith("%`vs-"))
return new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("http://hl7.org/fhir/ValueSet/"+s.substring(5, s.length()-1)+"").noExtensions()));
else if (s.startsWith("%`cs-"))
return new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("http://hl7.org/fhir/"+s.substring(5, s.length()-1)+"").noExtensions()));
else if (s.startsWith("%`ext-"))
return new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions();
return new ArrayList<>(Arrays.asList(new StringType("http://hl7.org/fhir/StructureDefinition/"+s.substring(6, s.length()-1)).noExtensions()));
else if (hostServices == null)
throw new PathEngineException("Unknown fixed constant '"+s+"'", expr.getStart(), expr.toString());
else
@ -2401,9 +2401,9 @@ public class FHIRPathEngine {
List<Base> result = new ArrayList<Base>();
if (atEntry && context.appInfo != null && hostServices != null) {
// we'll see if the name matches a constant known by the context.
Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), true);
List<Base> temp = hostServices.resolveConstant(context.appInfo, exp.getName(), true);
if (temp != null) {
result.add(temp);
result.addAll(temp);
return result;
}
}
@ -2415,9 +2415,9 @@ public class FHIRPathEngine {
if (atEntry && context.appInfo != null && hostServices != null && result.isEmpty()) {
// well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
// (if the name does match, and the user wants to get the constant value, they'll have to try harder...
Base temp = hostServices.resolveConstant(context.appInfo, exp.getName(), false);
List<Base> temp = hostServices.resolveConstant(context.appInfo, exp.getName(), false);
if (temp != null) {
result.add(temp);
result.addAll(temp);
}
}
return result;

View File

@ -1,38 +1,35 @@
package org.hl7.fhir.r4.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import org.hl7.fhir.exceptions.FHIRException;
@ -365,10 +362,10 @@ public class LiquidEngine implements IEvaluationContext {
}
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
LiquidEngineContext ctxt = (LiquidEngineContext) appContext;
if (ctxt.vars.containsKey(name))
return ctxt.vars.get(name);
return new ArrayList<>(Arrays.asList(ctxt.vars.get(name)));
if (externalHostServices == null)
return null;
return externalHostServices.resolveConstant(ctxt.externalContext, name, beforeContext);

View File

@ -168,12 +168,15 @@ public class StructureMapUtilities {
private class FFHIRPathHostServices implements IEvaluationContext{
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
Variables vars = (Variables) appContext;
Base res = vars.get(VariableMode.INPUT, name);
if (res == null)
res = vars.get(VariableMode.OUTPUT, name);
return res;
List<Base> result = new ArrayList<Base>();
if (res != null)
result.add(res);
return result;
}
@Override

View File

@ -11,10 +11,7 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -27,24 +24,24 @@ import javax.xml.parsers.ParserConfigurationException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Stream;
@Disabled
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FHIRPathTests {
public class FHIRPathTestEvaluationServices implements IEvaluationContext {
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstant), when item is element");
}
@Override
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.resolveConstantType), when item is element");
}
@ -136,9 +133,11 @@ public class FHIRPathTests {
@SuppressWarnings("deprecation")
@Disabled
@ParameterizedTest(name = "{index}: file {0}")
@MethodSource("data")
public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
fp.setHostServices(new FHIRPathTestEvaluationServices());
String input = test.getAttribute("inputfile");
String expression = XMLUtil.getNamedChild(test, "expression").getTextContent();
@ -211,4 +210,27 @@ public class FHIRPathTests {
}
}
}
@Test
@DisplayName("resolveConstant returns a list of Base")
public void resolveConstantReturnsList() {
final String DUMMY_CONSTANT_1 = "dummyConstant1";
final String DUMMY_CONSTANT_2 = "dummyConstant2";
fp.setHostServices(new FHIRPathTestEvaluationServices() {
@Override
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
return Arrays.asList(
new StringType(DUMMY_CONSTANT_1).noExtensions(),
new StringType(DUMMY_CONSTANT_2).noExtensions());
}
});
ExpressionNode expressionNode = fp.parse("%dummyConstant");
List<Base> result = fp.evaluate(null, expressionNode);
assertEquals(2, result.size());
assertEquals(DUMMY_CONSTANT_1, result.get(0).primitiveValue());
assertEquals(DUMMY_CONSTANT_2, result.get(1).primitiveValue());
}
}

View File

@ -286,7 +286,7 @@ public class SnapShotGenerationTests {
// FHIRPath methods
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
throw new Error("Not implemented yet");
}

View File

@ -2,11 +2,7 @@ package org.hl7.fhir.r4b.test;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.*;
import java.util.stream.Stream;
import javax.xml.parsers.ParserConfigurationException;
@ -17,14 +13,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r4b.formats.JsonParser;
import org.hl7.fhir.r4b.formats.XmlParser;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.BooleanType;
import org.hl7.fhir.r4b.model.ExpressionNode;
import org.hl7.fhir.r4b.model.PrimitiveType;
import org.hl7.fhir.r4b.model.Quantity;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.TypeDetails;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.*;
import org.hl7.fhir.r4b.test.FHIRPathTests.TestResultType;
import org.hl7.fhir.r4b.test.utils.TestingUtilities;
import org.hl7.fhir.r4b.utils.FHIRPathEngine;
@ -33,6 +22,8 @@ import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -41,6 +32,8 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FHIRPathTests {
public enum TestResultType {OK, SYNTAX, SEMANTICS, EXECUTION}
@ -273,4 +266,27 @@ public class FHIRPathTests {
}
}
}
@Test
@DisplayName("resolveConstant returns a list of Base")
public void resolveConstantReturnsList() {
final String DUMMY_CONSTANT_1 = "dummyConstant1";
final String DUMMY_CONSTANT_2 = "dummyConstant2";
fp.setHostServices(new FHIRPathTestEvaluationServices() {
@Override
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
return Arrays.asList(
new StringType(DUMMY_CONSTANT_1).noExtensions(),
new StringType(DUMMY_CONSTANT_2).noExtensions());
}
});
ExpressionNode expressionNode = fp.parse("%dummyConstant");
List<Base> result = fp.evaluate(null, expressionNode);
assertEquals(2, result.size());
assertEquals(DUMMY_CONSTANT_1, result.get(0).primitiveValue());
assertEquals(DUMMY_CONSTANT_2, result.get(1).primitiveValue());
}
}

View File

@ -2,11 +2,7 @@ package org.hl7.fhir.r5.test;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.*;
import java.util.stream.Stream;
import javax.xml.parsers.ParserConfigurationException;
@ -17,14 +13,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Quantity;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
@ -32,6 +21,8 @@ import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -40,6 +31,10 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FHIRPathTests {
public enum TestResultType {OK, SYNTAX, SEMANTICS, EXECUTION}
@ -229,7 +224,7 @@ public class FHIRPathTests {
List<Element> expected = new ArrayList<Element>();
XMLUtil.getNamedChildren(test, "output", expected);
Assertions.assertEquals(outcome.size(), expected.size(), String.format("Expected %d objects but found %d for expression %s", expected.size(), outcome.size(), expression));
assertEquals(outcome.size(), expected.size(), String.format("Expected %d objects but found %d for expression %s", expected.size(), outcome.size(), expression));
if ("false".equals(test.getAttribute("ordered"))) {
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
String tn = outcome.get(i).fhirType();
@ -252,7 +247,7 @@ public class FHIRPathTests {
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
String tn = expected.get(i).getAttribute("type");
if (!Utilities.noString(tn)) {
Assertions.assertEquals(tn, outcome.get(i).fhirType(), String.format("Outcome %d: Type should be %s but was %s", i, tn, outcome.get(i).fhirType()));
assertEquals(tn, outcome.get(i).fhirType(), String.format("Outcome %d: Type should be %s but was %s", i, tn, outcome.get(i).fhirType()));
}
String v = expected.get(i).getTextContent();
if (!Utilities.noString(v)) {
@ -265,11 +260,34 @@ public class FHIRPathTests {
System.out.println(name);
System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
}
Assertions.assertEquals(v, ((PrimitiveType) outcome.get(i)).fpValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
assertEquals(v, ((PrimitiveType) outcome.get(i)).fpValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, ((PrimitiveType) outcome.get(i)).fpValue(), expression));
}
}
}
}
}
}
@Test
@DisplayName("resolveConstant returns a list of Base")
public void resolveConstantReturnsList() {
final String DUMMY_CONSTANT_1 = "dummyConstant1";
final String DUMMY_CONSTANT_2 = "dummyConstant2";
fp.setHostServices(new FHIRPathTestEvaluationServices() {
@Override
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
return Arrays.asList(
new StringType(DUMMY_CONSTANT_1).noExtensions(),
new StringType(DUMMY_CONSTANT_2).noExtensions());
}
});
ExpressionNode expressionNode = fp.parse("%dummyConstant");
List<Base> result = fp.evaluate(null, expressionNode);
assertEquals(2, result.size());
assertEquals(DUMMY_CONSTANT_1, result.get(0).primitiveValue());
assertEquals(DUMMY_CONSTANT_2, result.get(1).primitiveValue());
}
}