From 5e30c0ee924995ed025fa387afe8e9c6603cdbca Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 9 Jul 2023 08:03:35 +1000 Subject: [PATCH] Fix checking FHIRPath statements on inner elements of type slices --- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 48 +++++++++++ .../type/StructureDefinitionValidator.java | 79 +++++++++++++++++-- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 688cedbaf..166388e88 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -639,6 +639,54 @@ public class FHIRPathEngine { return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, elementDependencies, true, false); } + /** + * check that paths referred to in the ExpressionNode are valid + * + * xPathStartsWithValueRef is a hack work around for the fact that FHIR Path sometimes needs a different starting point than the xpath + * + * returns a list of the possible types that might be returned by executing the ExpressionNode against a particular context + * + * @param context - the logical type against which this path is applied + * @throws DefinitionException + * @throws PathEngineException + * @if the path is not valid + */ + public TypeDetails checkOnTypes(Object appContext, String resourceType, List typeList, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException { + + // if context is a path that refers to a type, do that conversion now + TypeDetails types = new TypeDetails(CollectionStatus.SINGLETON); + for (String t : typeList) { + if (!t.contains(".")) { + StructureDefinition sd = worker.fetchTypeDefinition(t); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); + } + types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); + } else { + String ctxt = t.substring(0, t.indexOf('.')); + StructureDefinition sd = cu.findType(ctxt); + if (sd == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); + } + ElementDefinitionMatch ed = getElementDefinition(sd, t, true, expr); + if (ed == null) { + throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t); + } + if (ed.fixedType != null) { + types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); + } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { + types = new TypeDetails(CollectionStatus.SINGLETON, ctxt+"#"+t); + } else { + types = new TypeDetails(CollectionStatus.SINGLETON); + for (TypeRefComponent tt : ed.getDefinition().getType()) { + types.addType(tt.getCode()); + } + } + } + } + return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false); + } + /** * check that paths referred to in the ExpressionNode are valid * diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 1f950a540..da8783515 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -16,6 +16,7 @@ import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; +import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.elementmodel.Element; @@ -338,13 +339,13 @@ public class StructureDefinitionValidator extends BaseValidator { List elements = elementList.getChildrenByName("element"); int cc = 0; for (Element element : elements) { - ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint, invariantMap, rootPath, profileUrl) && ok; + ok = validateElementDefinition(errors, elements, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint, invariantMap, rootPath, profileUrl) && ok; cc++; } return ok; } - private boolean validateElementDefinition(List errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map invariantMap, String rootPath, String profileUrl) { + private boolean validateElementDefinition(List errors, List elements, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map invariantMap, String rootPath, String profileUrl) { boolean ok = true; boolean typeMustSupport = false; String path = element.getNamedChildValue("path"); @@ -472,13 +473,13 @@ public class StructureDefinitionValidator extends BaseValidator { List constraints = element.getChildrenByName("constraint"); int cc = 0; for (Element invariant : constraints) { - ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, element.getNamedChildValue("path"), rootPath, profileUrl) && ok; + ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl) && ok; cc++; } return ok; } - private boolean validateElementDefinitionInvariant(List errors, Element invariant, NodeStack stack, Map invariantMap, String path, String rootPath, String profileUrl) { + private boolean validateElementDefinitionInvariant(List errors, Element invariant, NodeStack stack, Map invariantMap, List elements, Element element, String path, String rootPath, String profileUrl) { boolean ok = true; String key = invariant.getNamedChildValue("key"); String expression = invariant.getNamedChildValue("expression"); @@ -494,8 +495,23 @@ public class StructureDefinitionValidator extends BaseValidator { } if (Utilities.noString(source) || (source.equals(profileUrl))) { // no need to revalidate FHIRPath from elsewhere try { - // String upath = profileUrl+"#"+path; - fpe.check(invariant, rootPath, path, fpe.parse(expression)); + // we have to figure out the context, and we might be in type slicing. + String exp = expression; + Element te = element; + List types = getTypesForElement(elements, te); + while (types.size() == 0 && te != null) { + Element oldte = te; + te = getParent(elements, te); + if (te != null) { + exp = tail(oldte, te)+"."+exp; + types = getTypesForElement(elements, te); + } + } + if (types.size() == 0) { + // we got to the root before finding anything typed + types.add(elements.get(0).getNamedChildValue("path")); + } + fpe.checkOnTypes(invariant, rootPath, types, fpe.parse(exp)); } catch (Exception e) { if (debug) { e.printStackTrace(); @@ -509,6 +525,57 @@ public class StructureDefinitionValidator extends BaseValidator { return ok; } + private String tail(Element te, Element newte) { + String p = te.getNamedChildValue("path"); + String pn = newte.getNamedChildValue("path"); + return p.substring(pn.length()+1); + } + + private Element getParent(List elements, Element te) { + int i = elements.indexOf(te) - 1; + String path = te.getNamedChildValue("path"); + while (i >= 0) { + String p = elements.get(i).getNamedChildValue("path"); + if (path.startsWith(p+".")) { + return elements.get(i); + } + i--; + } + return null; + } + + private List getTypesForElement(List elements, Element element) { + List types = new ArrayList<>(); + for (Element tr : element.getChildrenByName("type")) { + String t = tr.getNamedChildValue("code"); + if (t != null) { + if (isAbstractType(t) && hasChildren(element, elements) ) { + types.add(element.getNamedChildValue("path")); + } else { + types.add(t); + } + } + } + return types; + } + + private boolean hasChildren(Element element, List elements) { + int i = elements.indexOf(element); + String path = element.getNamedChildValue("path")+"."; + while (i < elements.size()) { + String p = elements.get(i).getNamedChildValue("path")+"."; + if (p.startsWith(path)) { + return true; + } + } + return false; + } + + private boolean isAbstractType(String t) { + StructureDefinition sd = context.fetchTypeDefinition(t); + return sd != null && sd.getAbstract(); + } + private boolean meaningWhenMissingAllowed(Element element) { // allowed to use meaningWhenMissing on the root of an element to say what it means when the extension // is not present.