FHIRPath - support lenient mode on polymorphics

This commit is contained in:
Grahame Grieve 2020-09-16 13:08:02 +10:00
parent 8a11bd2e9b
commit 66f0b35a38
4 changed files with 125 additions and 60 deletions

View File

@ -0,0 +1,5 @@
Validator:
* No changes
Other code changes:
* Support lenient mode on FIHRPath when referring to polymorphics

View File

@ -105,6 +105,7 @@ import ca.uhn.fhir.util.ElementUtil;
*
*/
public class FHIRPathEngine {
private enum Equality { Null, True, False }
private class FHIRConstant extends Base {
@ -211,6 +212,7 @@ public class FHIRPathEngine {
private ValidationOptions terminologyServiceOptions = new ValidationOptions();
private ProfileUtilities profileUtilities;
private String location; // for error messages
private boolean allowPolymorphicNames;
// if the fhir path expressions are allowed to use constants beyond those defined in the specification
// the application can implement them by providing a constant resolver
@ -372,10 +374,24 @@ public class FHIRPathEngine {
* @throws FHIRException
*/
protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException {
String tn = null;
if (isAllowPolymorphicNames()) {
// we'll look to see whether we hav a polymorphic name
for (Property p : item.children()) {
if (p.getName().endsWith("[x]")) {
String n = p.getName().substring(0, p.getName().length()-3);
if (name.startsWith(n)) {
tn = name.substring(n.length());
name = n;
break;
}
}
}
}
Base[] list = item.listChildrenByName(name, false);
if (list != null) {
for (Base v : list) {
if (v != null) {
if (v != null && (tn == null || v.fhirType().equalsIgnoreCase(tn))) {
result.add(v);
}
}
@ -5166,7 +5182,7 @@ public class FHIRPathEngine {
} else {
path = sdi.getSnapshot().getElement().get(0).getPath()+tail+"."+name;
ElementDefinitionMatch ed = getElementDefinition(sdi, path, false);
ElementDefinitionMatch ed = getElementDefinition(sdi, path, isAllowPolymorphicNames());
if (ed != null) {
if (!Utilities.noString(ed.getFixedType()))
result.addType(ed.getFixedType());
@ -5556,5 +5572,14 @@ public class FHIRPathEngine {
public IWorkerContext getWorker() {
return worker;
}
public boolean isAllowPolymorphicNames() {
return allowPolymorphicNames;
}
public void setAllowPolymorphicNames(boolean allowPolymorphicNames) {
this.allowPolymorphicNames = allowPolymorphicNames;
}
}

View File

@ -24,6 +24,7 @@ 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.test.FHIRPathTests.TestResultType;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
@ -41,6 +42,8 @@ import org.xml.sax.SAXException;
public class FHIRPathTests {
public enum TestResultType {OK, SYNTAX, SEMANTICS, EXECUTION}
public class FHIRPathTestEvaluationServices implements IEvaluationContext {
@Override
@ -111,6 +114,7 @@ public class FHIRPathTests {
XMLUtil.getNamedChildren(dom.getDocumentElement(), "group", groups);
for (Element g : groups) {
XMLUtil.getNamedChildren(g, "test", list);
XMLUtil.getNamedChildren(g, "modeTest", list);
}
List<Arguments> objects = new ArrayList<>();
@ -147,84 +151,115 @@ public class FHIRPathTests {
public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
// Setting timezone for this test. Grahame is in UTC+11, Travis is in GMT, and I'm here in Toronto, Canada with
// all my time based tests failing locally...
TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100"));
TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100"));
fp.setHostServices(new FHIRPathTestEvaluationServices());
String input = test.getAttribute("inputfile");
String expression = XMLUtil.getNamedChild(test, "expression").getTextContent();
boolean fail = Utilities.existsInList(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"), "true", "semantic");
TestResultType fail = TestResultType.OK;
if ("syntax".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
fail = TestResultType.SYNTAX;
} else if ("semantic".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
fail = TestResultType.SEMANTICS;
} else if ("execution".equals(XMLUtil.getNamedChild(test, "expression").getAttribute("invalid"))) {
fail = TestResultType.EXECUTION;
};
fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode")));
Resource res = null;
List<Base> outcome = new ArrayList<Base>();
ExpressionNode node = fp.parse(expression);
System.out.println(name);
ExpressionNode node = null;
try {
if (Utilities.noString(input)) {
fp.check(null, null, node);
} else {
res = resources.get(input);
if (res == null) {
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
resources.put(input, res);
}
fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node);
}
outcome = fp.evaluate(res, node);
Assertions.assertFalse(fail, String.format("Expected exception parsing %s", expression));
node = fp.parse(expression);
Assertions.assertTrue(fail != TestResultType.SYNTAX, String.format("Expected exception didn't occur parsing %s", expression));
} catch (Exception e) {
Assertions.assertTrue(fail, String.format("Unexpected exception parsing %s: " + e.getMessage(), expression));
Assertions.assertTrue(fail == TestResultType.SYNTAX, String.format("Unexpected exception parsing %s: " + e.getMessage(), expression));
}
if (node != null) {
try {
if (Utilities.noString(input)) {
fp.check(null, null, node);
} else {
res = resources.get(input);
if (res == null) {
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
resources.put(input, res);
}
fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node);
}
Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression));
} catch (Exception e) {
Assertions.assertTrue(fail == TestResultType.SEMANTICS, String.format("Unexpected exception checking %s: " + e.getMessage(), expression));
node = null;
}
}
if (node != null) {
try {
outcome = fp.evaluate(res, node);
Assertions.assertTrue(fail == TestResultType.OK, String.format("Expected exception didn't occur executing %s", expression));
} catch (Exception e) {
Assertions.assertTrue(fail == TestResultType.EXECUTION, String.format("Unexpected exception executing %s: " + e.getMessage(), expression));
node = null;
}
}
if ("true".equals(test.getAttribute("predicate"))) {
boolean ok = fp.convertToBoolean(outcome);
outcome.clear();
outcome.add(new BooleanType(ok));
}
System.out.println(name);
if (fp.hasLog()) {
System.out.println(name);
System.out.println(fp.takeLog());
}
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));
if ("false".equals(test.getAttribute("ordered"))) {
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
String tn = outcome.get(i).fhirType();
String s;
if (outcome.get(i) instanceof Quantity) {
s = fp.convertToString(outcome.get(i));
} else {
s = ((PrimitiveType) outcome.get(i)).asStringValue();
}
boolean found = false;
for (Element e : expected) {
if ((Utilities.noString(e.getAttribute("type")) || e.getAttribute("type").equals(tn)) &&
(Utilities.noString(e.getTextContent()) || e.getTextContent().equals(s))) {
found = true;
}
}
Assertions.assertTrue(found, String.format("Outcome %d: Value %s of type %s not expected for %s", i, s, tn, expression));
if (node != null) {
if ("true".equals(test.getAttribute("predicate"))) {
boolean ok = fp.convertToBoolean(outcome);
outcome.clear();
outcome.add(new BooleanType(ok));
}
} else {
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()));
}
String v = expected.get(i).getTextContent();
if (!Utilities.noString(v)) {
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));
if ("false".equals(test.getAttribute("ordered"))) {
for (int i = 0; i < Math.min(outcome.size(), expected.size()); i++) {
String tn = outcome.get(i).fhirType();
String s;
if (outcome.get(i) instanceof Quantity) {
Quantity q = fp.parseQuantityString(v);
Assertions.assertTrue(outcome.get(i).equalsDeep(q), String.format("Outcome %d: Value should be %s but was %s", i, v, outcome.get(i).toString()));
s = fp.convertToString(outcome.get(i));
} else {
Assertions.assertTrue(outcome.get(i) instanceof PrimitiveType, String.format("Outcome %d: Value should be a primitive type but was %s", i, outcome.get(i).fhirType()));
if (!(v.equals(((PrimitiveType) outcome.get(i)).asStringValue()))) {
System.out.println(name);
System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, outcome.get(i).toString(), expression));
s = ((PrimitiveType) outcome.get(i)).asStringValue();
}
boolean found = false;
for (Element e : expected) {
if ((Utilities.noString(e.getAttribute("type")) || e.getAttribute("type").equals(tn)) &&
(Utilities.noString(e.getTextContent()) || e.getTextContent().equals(s))) {
found = true;
}
}
Assertions.assertTrue(found, String.format("Outcome %d: Value %s of type %s not expected for %s", i, s, tn, expression));
}
} else {
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()));
}
String v = expected.get(i).getTextContent();
if (!Utilities.noString(v)) {
if (outcome.get(i) instanceof Quantity) {
Quantity q = fp.parseQuantityString(v);
Assertions.assertTrue(outcome.get(i).equalsDeep(q), String.format("Outcome %d: Value should be %s but was %s", i, v, outcome.get(i).toString()));
} else {
Assertions.assertTrue(outcome.get(i) instanceof PrimitiveType, String.format("Outcome %d: Value should be a primitive type but was %s", i, outcome.get(i).fhirType()));
if (!(v.equals(((PrimitiveType) outcome.get(i)).asStringValue()))) {
System.out.println(name);
System.out.println(String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, outcome.get(i).toString(), expression));
}
Assertions.assertEquals(v, ((PrimitiveType) outcome.get(i)).asStringValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, outcome.get(i).toString(), expression));
}
Assertions.assertEquals(v, ((PrimitiveType) outcome.get(i)).asStringValue(), String.format("Outcome %d: Value should be %s but was %s for expression %s", i, v, outcome.get(i).toString(), expression));
}
}
}

View File

@ -17,7 +17,7 @@
<properties>
<hapi_fhir_version>5.1.0</hapi_fhir_version>
<validator_test_case_version>1.1.40</validator_test_case_version>
<validator_test_case_version>1.1.41</validator_test_case_version>
<junit_jupiter_version>5.6.2</junit_jupiter_version>
<maven_surefire_version>3.0.0-M4</maven_surefire_version>
<jacoco_version>0.8.5</jacoco_version>