More improved FHIRPath checking when validating

This commit is contained in:
Grahame Grieve 2023-07-31 14:37:41 +10:00
parent 5026ff3506
commit b42c908328
5 changed files with 61 additions and 5 deletions

View File

@ -42,6 +42,7 @@ import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
@ -115,6 +116,16 @@ public class TypeDetails {
public boolean isSystemType() {
return uri.startsWith(FP_NS);
}
public String describeMin() {
if (uri.startsWith(FP_NS)) {
return "System."+uri.substring(FP_NS.length());
}
if (uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
return "FHIR."+uri.substring("http://hl7.org/fhir/StructureDefinition/".length());
}
return uri;
}
}
@ -399,6 +410,12 @@ public class TypeDetails {
public String describe() {
return getTypes().toString();
}
public String describeMin() {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ProfiledType pt : types)
b.append(pt.describeMin());
return b.toString();
}
public String getType() {
for (ProfiledType pt : types)
return pt.uri;

View File

@ -3387,8 +3387,12 @@ public class FHIRPathEngine {
return td;
}
case OfType : {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, exp.getParameters().get(0).getName());
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
String tn = exp.getParameters().get(0).getName();
if (typeCastIsImpossible(focus, tn)) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()));
}
TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn);
if (td.typesHaveTargets()) {
td.addTargets(focus.getTargets());
}
@ -3707,6 +3711,10 @@ public class FHIRPathEngine {
throw new Error("not Implemented yet");
}
private boolean typeCastIsImpossible(TypeDetails focus, String tn) {
return !focus.hasType(tn);
}
private boolean isExpressionParameter(ExpressionNode exp, int i) {
switch (i) {
case 0:

View File

@ -947,6 +947,8 @@ public class I18nConstants {
public static final String FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT = "FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT";
public static final String FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT = "FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT";
public static final String ED_INVARIANT_KEY_ALREADY_USED = "ED_INVARIANT_KEY_ALREADY_USED";
public static final String FHIRPATH_OFTYPE_IMPOSSIBLE = "FHIRPATH_OFTYPE_IMPOSSIBLE";
public static final String ED_SEARCH_EXPRESSION_ERROR = "ED_SEARCH_EXPRESSION_ERROR";

View File

@ -1003,3 +1003,5 @@ LIQUID_VARIABLE_ILLEGAL = Liquid Exception: The variable name ''{0}'' cannot be
ED_INVARIANT_DIFF_NO_SOURCE = The invariant {0} defined in the differential must have no source, or the source must be the same as the profile
FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT = The left side is inherently a collection, and so the expression ''{0}'' may fail or return false if there is more than one item in the content being evaluated
FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT = The right side is inherently a collection, and so this expression ''{0}'' may fail or return false if there is more than one item in the content being evaluated
FHIRPATH_OFTYPE_IMPOSSIBLE = The type specified in ofType is {1} which is not a possible candidate for the existing types ({0}) in the expression {2}. Check the paths and types to be sure this is what is intended
ED_SEARCH_EXPRESSION_ERROR = Error in search expression ''{0}'': {1}

View File

@ -46,9 +46,16 @@ public class SearchParameterValidator extends BaseValidator {
public boolean validateSearchParameter(List<ValidationMessage> errors, Element cs, NodeStack stack) {
boolean ok = true;
String url = cs.getNamedChildValue("url");
String master = cs.getNamedChildValue("derivedFrom");
// String url = cs.getNamedChildValue("url");
if (cs.hasChild("expression")) {
List<String> bases = new ArrayList<>();
for (Element b : cs.getChildrenByName("base")) {
bases.add(b.primitiveValue());
}
ok = checkExpression(errors, stack.push(cs.getNamedChild("expression"), -1, null, null), cs.getNamedChildValue("expression"), bases) && ok;
}
String master = cs.getNamedChildValue("derivedFrom");
if (!Utilities.noString(master)) {
SearchParameter sp = context.fetchResource(SearchParameter.class, master);
if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE,stack.getLiteralPath(), sp != null, I18nConstants.SEARCHPARAMETER_NOTFOUND, master)) {
@ -67,13 +74,33 @@ public class SearchParameterValidator extends BaseValidator {
String expOther = canonicalise(sp.getExpression(), bases);
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE,stack.getLiteralPath(), expThis.equals(expOther), I18nConstants.SEARCHPARAMETER_EXP_WRONG, master, sp.getExpression(), cs.getNamedChildValue("expression"));
}
// todo: check compositions
}
}
return ok;
}
private String canonicalise(String path, List<String> bases) {
private boolean checkExpression(List<ValidationMessage> errors, NodeStack stack, String expression, List<String> bases) {
boolean ok = true;
try {
List<String> warnings = new ArrayList<>();
fpe.checkOnTypes(null, null, bases, fpe.parse(expression), warnings);
for (String s : warnings) {
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, s);
}
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
ok = rule(errors, "2023-06-19", IssueType.INVALID, stack, false, I18nConstants.ED_SEARCH_EXPRESSION_ERROR, expression, e.getMessage()) && ok;
}
return ok;
}
private String canonicalise(String path, List<String> bases) {
ExpressionNode exp = fpe.parse(path);
List<ExpressionNode> pass = new ArrayList<>();
while (exp != null) {