Merge pull request #1341 from hapifhir/2023-07-gg-r5-fhirpath-type-slices

Fix checking FHIRPath statements on inner elements of type slices
This commit is contained in:
Grahame Grieve 2023-07-12 06:06:31 +10:00 committed by GitHub
commit eef3ebc5f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 121 additions and 6 deletions

View File

@ -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<String> 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
*

View File

@ -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<Element> 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<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map<String, String> invariantMap, String rootPath, String profileUrl) {
private boolean validateElementDefinition(List<ValidationMessage> errors, List<Element> elements, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, Map<String, String> 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<Element> 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<ValidationMessage> errors, Element invariant, NodeStack stack, Map<String, String> invariantMap, String path, String rootPath, String profileUrl) {
private boolean validateElementDefinitionInvariant(List<ValidationMessage> errors, Element invariant, NodeStack stack, Map<String, String> invariantMap, List<Element> 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<String> 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<Element> 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<String> getTypesForElement(List<Element> elements, Element element) {
List<String> 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<Element> 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.