update error handling to preserve message ids properly

This commit is contained in:
Grahame Grieve 2023-11-09 16:51:15 +11:00
parent f07a779d36
commit dc791becaa
10 changed files with 138 additions and 60 deletions

View File

@ -62,6 +62,7 @@ import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.TypeDetails.ProfiledType; import org.hl7.fhir.r5.model.TypeDetails.ProfiledType;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IssueMessage;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FHIRConstant; import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FHIRConstant;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.ClassTypeInfo; import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.ClassTypeInfo;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails; import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails;
@ -119,6 +120,26 @@ import ca.uhn.fhir.util.ElementUtil;
*/ */
public class FHIRPathEngine { public class FHIRPathEngine {
public class IssueMessage {
private String message;
private String id;
public IssueMessage(String message, String id) {
this.message = message;
this.id = id;
}
public String getMessage() {
return message;
}
public String getId() {
return id;
}
}
private enum Equality { Null, True, False } private enum Equality { Null, True, False }
private IWorkerContext worker; private IWorkerContext worker;
@ -136,7 +157,7 @@ public class FHIRPathEngine {
private boolean doNotEnforceAsSingletonRule; private boolean doNotEnforceAsSingletonRule;
private boolean doNotEnforceAsCaseSensitive; private boolean doNotEnforceAsCaseSensitive;
private boolean allowDoubleQuotes; private boolean allowDoubleQuotes;
private List<String> typeWarnings = new ArrayList<>(); private List<IssueMessage> typeWarnings = new ArrayList<>();
private boolean emitSQLonFHIRWarning; private boolean emitSQLonFHIRWarning;
// if the fhir path expressions are allowed to use constants beyond those defined in the specification // if the fhir path expressions are allowed to use constants beyond those defined in the specification
@ -499,7 +520,7 @@ public class FHIRPathEngine {
* @throws PathEngineException * @throws PathEngineException
* @if the path is not valid * @if the path is not valid
*/ */
public TypeDetails checkOnTypes(Object appContext, String resourceType, List<String> typeList, ExpressionNode expr, List<String> warnings) throws FHIRLexerException, PathEngineException, DefinitionException { public TypeDetails checkOnTypes(Object appContext, String resourceType, List<String> typeList, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
typeWarnings.clear(); typeWarnings.clear();
// if context is a path that refers to a type, do that conversion now // if context is a path that refers to a type, do that conversion now
@ -550,7 +571,7 @@ public class FHIRPathEngine {
return res; return res;
} }
public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List<String> warnings) throws FHIRLexerException, PathEngineException, DefinitionException { public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List<IssueMessage> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
typeWarnings.clear(); typeWarnings.clear();
TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false, expr); TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false, expr);
warnings.addAll(typeWarnings); warnings.addAll(typeWarnings);
@ -589,9 +610,9 @@ public class FHIRPathEngine {
fmt = fmt + " "+worker.formatMessagePlural(num, I18nConstants.FHIRPATH_LOCATION, location); fmt = fmt + " "+worker.formatMessagePlural(num, I18nConstants.FHIRPATH_LOCATION, location);
} }
if (holder != null) { if (holder != null) {
return new PathEngineException(fmt, holder.getStart(), holder.toString()); return new PathEngineException(fmt, constName, holder.getStart(), holder.toString());
} else { } else {
return new PathEngineException(fmt); return new PathEngineException(fmt, constName);
} }
} }
@ -601,9 +622,9 @@ public class FHIRPathEngine {
fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location); fmt = fmt + " "+worker.formatMessage(I18nConstants.FHIRPATH_LOCATION, location);
} }
if (holder != null) { if (holder != null) {
return new PathEngineException(fmt, holder.getStart(), holder.toString()); return new PathEngineException(fmt, constName, holder.getStart(), holder.toString());
} else { } else {
return new PathEngineException(fmt); return new PathEngineException(fmt, constName);
} }
} }
@ -1600,10 +1621,10 @@ public class FHIRPathEngine {
// special Logic for SQL-on-FHIR: // special Logic for SQL-on-FHIR:
if (focus.isChoice()) { if (focus.isChoice()) {
if (expr.getInner() == null || expr.getInner().getFunction() != Function.OfType) { if (expr.getInner() == null || expr.getInner().getFunction() != Function.OfType) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER, expr.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_NO_TYPE_SPECIFIER));
} }
} else if (expr.getInner() != null && expr.getInner().getFunction() == Function.OfType) { } else if (expr.getInner() != null && expr.getInner().getFunction() == Function.OfType) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER, expr.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER, expr.toString()), I18nConstants.FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER));
} }
} }
} }
@ -1816,10 +1837,10 @@ public class FHIRPathEngine {
} else { } else {
String tn = convertToString(right); String tn = convertToString(right);
if (!isKnownType(tn)) { if (!isKnownType(tn)) {
throw new PathEngineException("The type "+tn+" is not valid"); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE);
} }
if (!doNotEnforceAsSingletonRule && left.size() > 1) { if (!doNotEnforceAsSingletonRule && left.size() > 1) {
throw new PathEngineException("Attempt to use as on more than one item ("+left.size()+", '"+expr.toString()+"')"); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, left.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION);
} }
for (Base nextLeft : left) { for (Base nextLeft : left) {
if (compareTypeNames(tn, nextLeft.fhirType())) { if (compareTypeNames(tn, nextLeft.fhirType())) {
@ -1904,18 +1925,18 @@ public class FHIRPathEngine {
private void checkCardinalityForComparabilitySame(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { private void checkCardinalityForComparabilitySame(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
if (left.isList() && !right.isList()) { if (left.isList() && !right.isList()) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT));
} else if (!left.isList() && right.isList()) { } else if (!left.isList() && right.isList()) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT));
} }
} }
private void checkCardinalityForSingle(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) { private void checkCardinalityForSingle(TypeDetails left, Operation operation, TypeDetails right, ExpressionNode expr) {
if (left.isList()) { if (left.isList()) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT));
} }
if (right.isList()) { if (right.isList()) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT, expr.toString()), I18nConstants.FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT));
} }
} }
@ -1995,7 +2016,7 @@ public class FHIRPathEngine {
if (right.hasType(worker, "Quantity")) { if (right.hasType(worker, "Quantity")) {
result.addType(left.getType()); result.addType(left.getType());
} else { } else {
throw new PathEngineException(String.format("Error in date arithmetic: Unable to add type {0} to {1}", right.getType(), left.getType()), expr.getOpStart(), expr.toString()); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_PLUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_PLUS, expr.getOpStart(), expr.toString());
} }
} }
return result; return result;
@ -2012,7 +2033,7 @@ public class FHIRPathEngine {
if (right.hasType(worker, "Quantity")) { if (right.hasType(worker, "Quantity")) {
result.addType(left.getType()); result.addType(left.getType());
} else { } else {
throw new PathEngineException(String.format("Error in date arithmetic: Unable to subtract type {0} from {1}", right.getType(), left.getType())); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_MINUS, right.getType(), left.getType()), I18nConstants.FHIRPATH_ARITHMETIC_MINUS, expr.getOpStart(), expr.toString());
} }
} }
return result; return result;
@ -2654,13 +2675,13 @@ public class FHIRPathEngine {
result.add(Calendar.YEAR, value); result.add(Calendar.YEAR, value);
break; break;
case "a": case "a":
throw new PathEngineException(String.format("Error in date arithmetic: attempt to add a definite quantity duration time unit %s", q.getCode())); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString());
case "months": case "months":
case "month": case "month":
result.add(Calendar.MONTH, value); result.add(Calendar.MONTH, value);
break; break;
case "mo": case "mo":
throw new PathEngineException(String.format("Error in date arithmetic: attempt to add a definite quantity duration time unit %s", q.getCode()), holder.getOpStart(), holder.toString()); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_QTY, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_QTY, holder.getOpStart(), holder.toString());
case "weeks": case "weeks":
case "week": case "week":
case "wk": case "wk":
@ -2692,7 +2713,7 @@ public class FHIRPathEngine {
result.add(Calendar.MILLISECOND, value); result.add(Calendar.MILLISECOND, value);
break; break;
default: default:
throw new PathEngineException(String.format("Error in date arithmetic: unrecognized time unit %s", q.getCode())); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_ARITHMETIC_UNIT, q.getCode()), I18nConstants.FHIRPATH_ARITHMETIC_UNIT, holder.getOpStart(), holder.toString());
} }
return result; return result;
} }
@ -2730,7 +2751,7 @@ public class FHIRPathEngine {
p = worker.getUcumService().multiply(pl, pr); p = worker.getUcumService().multiply(pl, pr);
result.add(pairToQty(p)); result.add(pairToQty(p));
} catch (UcumException e) { } catch (UcumException e) {
throw new PathEngineException(e.getMessage(), expr.getOpStart(), expr.toString(), e); throw new PathEngineException(e.getMessage(), null, expr.getOpStart(), expr.toString(), e); // #FIXME
} }
} else { } else {
throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType()); throw makeException(expr, I18nConstants.FHIRPATH_OP_INCOMPATIBLE, "*", left.get(0).fhirType(), right.get(0).fhirType());
@ -3221,7 +3242,7 @@ public class FHIRPathEngine {
} }
if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) { if (exp.getFunction() == Function.First || exp.getFunction() == Function.Last || exp.getFunction() == Function.Tail || exp.getFunction() == Function.Skip || exp.getFunction() == Function.Take) {
if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) { if (focus.getCollectionStatus() == CollectionStatus.SINGLETON) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_NOT_A_COLLECTION, container.toString()), I18nConstants.FHIRPATH_NOT_A_COLLECTION));
} }
} }
@ -3297,7 +3318,7 @@ public class FHIRPathEngine {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
String tn = exp.getParameters().get(0).getName(); String tn = exp.getParameters().get(0).getName();
if (typeCastIsImpossible(focus, tn)) { if (typeCastIsImpossible(focus, tn)) {
typeWarnings.add(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString())); typeWarnings.add(new IssueMessage(worker.formatMessage(I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE, focus.describeMin(), tn, exp.toString()), I18nConstants.FHIRPATH_OFTYPE_IMPOSSIBLE));
} }
TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn); TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON, tn);
if (td.typesHaveTargets()) { if (td.typesHaveTargets()) {
@ -4871,10 +4892,10 @@ public class FHIRPathEngine {
tn = "FHIR."+expr.getParameters().get(0).getName(); tn = "FHIR."+expr.getParameters().get(0).getName();
} }
if (!isKnownType(tn)) { if (!isKnownType(tn)) {
throw new PathEngineException("The type "+tn+" is not valid"); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); // #FIXME
} }
if (!doNotEnforceAsSingletonRule && focus.size() > 1) { if (!doNotEnforceAsSingletonRule && focus.size() > 1) {
throw new PathEngineException("Attempt to use as() on more than one item ("+focus.size()+")"); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_AS_COLLECTION, focus.size(), expr.toString()), I18nConstants.FHIRPATH_AS_COLLECTION); // #FIXME
} }
for (Base b : focus) { for (Base b : focus) {
@ -4914,7 +4935,7 @@ public class FHIRPathEngine {
tn = "FHIR."+expr.getParameters().get(0).getName(); tn = "FHIR."+expr.getParameters().get(0).getName();
} }
if (!isKnownType(tn)) { if (!isKnownType(tn)) {
throw new PathEngineException("The type "+tn+" is not valid"); throw new PathEngineException(worker.formatMessage(I18nConstants.FHIRPATH_INVALID_TYPE, tn), I18nConstants.FHIRPATH_INVALID_TYPE); // #FIXME
} }

View File

@ -54,10 +54,12 @@ public class OperationOutcomeUtilities {
issue.addExpression(message.getLocation()); issue.addExpression(message.getLocation());
} }
// pass through line/col if they're present // pass through line/col if they're present
if (message.getLine() >= 0) if (message.getLine() >= 0) {
issue.addExtension().setUrl(ToolingExtensions.EXT_ISSUE_LINE).setValue(new IntegerType(message.getLine())); issue.addExtension().setUrl(ToolingExtensions.EXT_ISSUE_LINE).setValue(new IntegerType(message.getLine()));
if (message.getCol() >= 0) }
if (message.getCol() >= 0) {
issue.addExtension().setUrl(ToolingExtensions.EXT_ISSUE_COL).setValue(new IntegerType(message.getCol())); issue.addExtension().setUrl(ToolingExtensions.EXT_ISSUE_COL).setValue(new IntegerType(message.getCol()));
}
issue.setSeverity(convert(message.getLevel())); issue.setSeverity(convert(message.getLevel()));
CodeableConcept c = new CodeableConcept(); CodeableConcept c = new CodeableConcept();
c.setText(message.getMessage()); c.setText(message.getMessage());

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ExpressionNode; import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus; import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IssueMessage;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonArray; import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonBoolean; import org.hl7.fhir.utilities.json.model.JsonBoolean;
@ -253,7 +254,7 @@ public class Validator {
} else { } else {
String expr = expression.asString(); String expr = expression.asString();
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
TypeDetails td = null; TypeDetails td = null;
ExpressionNode node = null; ExpressionNode node = null;
try { try {
@ -264,8 +265,8 @@ public class Validator {
error(path, expression, e.getMessage(), IssueType.INVALID); error(path, expression, e.getMessage(), IssueType.INVALID);
} }
if (td != null && node != null) { if (td != null && node != null) {
for (String s : warnings) { for (IssueMessage s : warnings) {
warning(path+".path", expression, s); warning(path+".path", expression, s.getMessage());
} }
String columnName = null; String columnName = null;
JsonElement nameJ = column.get("name"); JsonElement nameJ = column.get("name");
@ -419,7 +420,7 @@ public class Validator {
} else { } else {
String expr = expression.asString(); String expr = expression.asString();
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
TypeDetails td = null; TypeDetails td = null;
try { try {
ExpressionNode n = fpe.parse(expr); ExpressionNode n = fpe.parse(expr);
@ -429,8 +430,8 @@ public class Validator {
error(path, expression, e.getMessage(), IssueType.INVALID); error(path, expression, e.getMessage(), IssueType.INVALID);
} }
if (td != null) { if (td != null) {
for (String s : warnings) { for (IssueMessage s : warnings) {
warning(path+".forEach", expression, s); warning(path+".forEach", expression, s.getMessage());
} }
} }
return td; return td;
@ -444,7 +445,7 @@ public class Validator {
} else { } else {
String expr = expression.asString(); String expr = expression.asString();
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
TypeDetails td = null; TypeDetails td = null;
try { try {
ExpressionNode n = fpe.parse(expr); ExpressionNode n = fpe.parse(expr);
@ -454,8 +455,8 @@ public class Validator {
error(path, expression, e.getMessage(), IssueType.INVALID); error(path, expression, e.getMessage(), IssueType.INVALID);
} }
if (td != null) { if (td != null) {
for (String s : warnings) { for (IssueMessage s : warnings) {
warning(path+".forEachOrNull", expression, s); warning(path+".forEachOrNull", expression, s.getMessage());
} }
} }
return td; return td;
@ -546,7 +547,7 @@ public class Validator {
error(path, where, "No path provided", IssueType.REQUIRED); error(path, where, "No path provided", IssueType.REQUIRED);
} }
List<String> types = new ArrayList<>(); List<String> types = new ArrayList<>();
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
types.add(resourceName); types.add(resourceName);
TypeDetails td = null; TypeDetails td = null;
try { try {
@ -560,8 +561,8 @@ public class Validator {
if (td.getCollectionStatus() != CollectionStatus.SINGLETON || td.getTypes().size() != 1 || !td.hasType("boolean")) { if (td.getCollectionStatus() != CollectionStatus.SINGLETON || td.getTypes().size() != 1 || !td.hasType("boolean")) {
error(path+".path", where.get("path"), "A where path must return a boolean, but the expression "+expr+" returns a "+td.describe(), IssueType.BUSINESSRULE); error(path+".path", where.get("path"), "A where path must return a boolean, but the expression "+expr+" returns a "+td.describe(), IssueType.BUSINESSRULE);
} else { } else {
for (String s : warnings) { for (IssueMessage s : warnings) {
warning(path+".path", where.get("path"), s); warning(path+".path", where.get("path"), s.getMessage());
} }
} }
} }

View File

@ -39,10 +39,8 @@ public class PathEngineException extends FHIRException {
private static final long serialVersionUID = 31969342112856390L; private static final long serialVersionUID = 31969342112856390L;
private SourceLocation location; private SourceLocation location;
private String expression; private String expression;
private String id;
public PathEngineException() {
super();
}
public PathEngineException(String message, Throwable cause) { public PathEngineException(String message, Throwable cause) {
super(message, cause); super(message, cause);
@ -60,6 +58,26 @@ public class PathEngineException extends FHIRException {
super(message+rep(location, expression)); super(message+rep(location, expression));
} }
public PathEngineException(String message, String id, Throwable cause) {
super(message, cause);
this.id = id;
}
public PathEngineException(String message, String id) {
super(message);
this.id = id;
}
public PathEngineException(String message, String id, SourceLocation location, String expression, Throwable cause) {
super(message+rep(location, expression), cause);
this.id = id;
}
public PathEngineException(String message, String id, SourceLocation location, String expression) {
super(message+rep(location, expression));
this.id = id;
}
private static String rep(SourceLocation loc, String expr) { private static String rep(SourceLocation loc, String expr) {
if (loc != null) { if (loc != null) {
if (loc.getLine() == 1) { if (loc.getLine() == 1) {
@ -94,4 +112,8 @@ public class PathEngineException extends FHIRException {
this.location = location; this.location = location;
} }
public String getId() {
return id;
}
} }

View File

@ -1015,6 +1015,12 @@ public class I18nConstants {
public static final String UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = "UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV"; public static final String UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = "UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV";
public static final String ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = "ED_CONTEXT_INVARIANT_EXPRESSION_ERROR"; public static final String ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = "ED_CONTEXT_INVARIANT_EXPRESSION_ERROR";
public static final String VALIDATION_VAL_PROFILE_SIGNPOST_OBS = "VALIDATION_VAL_PROFILE_SIGNPOST_OBS"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_OBS = "VALIDATION_VAL_PROFILE_SIGNPOST_OBS";
public static final String FHIRPATH_INVALID_TYPE = "FHIRPATH_INVALID_TYPE";
public static final String FHIRPATH_AS_COLLECTION = "FHIRPATH_AS_COLLECTION";
public static final String FHIRPATH_ARITHMETIC_QTY = "FHIRPATH_ARITHMETIC_QTY";
public static final String FHIRPATH_ARITHMETIC_UNIT = "FHIRPATH_ARITHMETIC_UNIT";
public static final String FHIRPATH_ARITHMETIC_PLUS = "FHIRPATH_ARITHMETIC_PLUS";
public static final String FHIRPATH_ARITHMETIC_MINUS = "FHIRPATH_ARITHMETIC_MINUS";
} }

View File

@ -1072,4 +1072,9 @@ CDA_UNKNOWN_TEMPLATE_EXT = The CDA Template {0} / {1} is not known
UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = The types could not be determined from the extension context, so the invariant can't be validated (types = {0}) UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = The types could not be determined from the extension context, so the invariant can't be validated (types = {0})
ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = Error in constraint ''{0}'': {1} ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = Error in constraint ''{0}'': {1}
VALIDATION_VAL_PROFILE_SIGNPOST_OBS = Validate Observation against {1} profile because the {2} code {3} was found VALIDATION_VAL_PROFILE_SIGNPOST_OBS = Validate Observation against {1} profile because the {2} code {3} was found
FHIRPATH_INVALID_TYPE = The type {0} is not valid
FHIRPATH_AS_COLLECTION = Attempt to use ''as()'' on more than one item (''{0}'', ''{1}'')
FHIRPATH_ARITHMETIC_QTY = Error in date arithmetic: attempt to add a definite quantity duration time unit {0}
FHIRPATH_ARITHMETIC_UNIT = Error in date arithmetic: unrecognized time unit {0}
FHIRPATH_ARITHMETIC_PLUS = Error in date arithmetic: Unable to add type {0} to {1}
FHIRPATH_ARITHMETIC_MINUS = Error in date arithmetic: Unable to subtract type {0} to {1}

View File

@ -448,7 +448,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
protected boolean ruleInv(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, String invId, Object... theMessageArguments) { protected boolean ruleInv(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, String invId, Object... theMessageArguments) {
if (!thePass && doingErrors()) { if (!thePass && doingErrors()) {
String message = context.formatMessage(theMessage, theMessageArguments); String message = context.formatMessage(theMessage, theMessageArguments);
addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage).setInvId(invId); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, invId).setInvId(invId);
} }
return thePass; return thePass;
} }
@ -472,7 +472,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
protected boolean txRule(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { protected boolean txRule(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass && doingErrors()) { if (!thePass && doingErrors()) {
String message = context.formatMessage(theMessage, theMessageArguments); String message = context.formatMessage(theMessage, theMessageArguments);
ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(theMessage); ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(idForMessage(theMessage, message));
vm.setRuleDate(ruleDate); vm.setRuleDate(ruleDate);
if (checkMsgId(theMessage, vm)) { if (checkMsgId(theMessage, vm)) {
errors.add(vm.setTxLink(txLink)); errors.add(vm.setTxLink(txLink));
@ -481,6 +481,10 @@ public class BaseValidator implements IValidationContextResourceLoader {
return thePass; return thePass;
} }
private String idForMessage(String theMessage, String message) {
return theMessage.equals(message) ? null : theMessage;
}
/** /**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
* *
@ -605,18 +609,33 @@ public class BaseValidator implements IValidationContextResourceLoader {
} }
protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String id, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass && doingWarnings()) {
String nmsg = context.formatMessage(msg, theMessageArguments);
IssueSeverity severity = IssueSeverity.WARNING;
addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, id);
}
return thePass;
}
protected boolean warningInv(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, String invId, Object... theMessageArguments) { protected boolean warningInv(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, String invId, Object... theMessageArguments) {
if (!thePass && doingWarnings()) { if (!thePass && doingWarnings()) {
String nmsg = context.formatMessage(msg, theMessageArguments); String nmsg = context.formatMessage(msg, theMessageArguments);
IssueSeverity severity = IssueSeverity.WARNING; IssueSeverity severity = IssueSeverity.WARNING;
addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg).setInvId(invId); String id = idForMessage(msg, nmsg);
addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, id).setMessageId(id).setInvId(invId);
} }
return thePass; return thePass;
} }
protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, NodeStack stack, boolean thePass, String msg, Object... theMessageArguments) { protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, NodeStack stack, boolean thePass, String msg, Object... theMessageArguments) {
return warning(errors, ruleDate, type, stack.line(), stack.col(), stack.getLiteralPath(), thePass, msg, theMessageArguments); return warning(errors, ruleDate, type, stack, null, thePass, msg, theMessageArguments);
}
protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, NodeStack stack, String id, boolean thePass, String msg, Object... theMessageArguments) {
return warning(errors, ruleDate, type, stack.line(), stack.col(), stack.getLiteralPath(), id, thePass, msg, theMessageArguments);
} }
protected boolean warningPlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String msg, Object... theMessageArguments) { protected boolean warningPlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String msg, Object... theMessageArguments) {
@ -664,7 +683,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
protected boolean txWarning(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { protected boolean txWarning(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass && doingWarnings()) { if (!thePass && doingWarnings()) {
String nmsg = context.formatMessage(msg, theMessageArguments); String nmsg = context.formatMessage(msg, theMessageArguments);
ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg); ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(idForMessage(msg, nmsg));
vmsg.setRuleDate(ruleDate); vmsg.setRuleDate(ruleDate);
if (checkMsgId(msg, vmsg)) { if (checkMsgId(msg, vmsg)) {
errors.add(vmsg); errors.add(vmsg);

View File

@ -11,6 +11,7 @@ import org.hl7.fhir.r5.model.ExpressionNode.Kind;
import org.hl7.fhir.r5.model.ExpressionNode.Operation; import org.hl7.fhir.r5.model.ExpressionNode.Operation;
import org.hl7.fhir.r5.model.SearchParameter; import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IssueMessage;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -77,10 +78,10 @@ public class SearchParameterValidator extends BaseValidator {
private boolean checkExpression(List<ValidationMessage> errors, NodeStack stack, String expression, List<String> bases) { private boolean checkExpression(List<ValidationMessage> errors, NodeStack stack, String expression, List<String> bases) {
boolean ok = true; boolean ok = true;
try { try {
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
fpe.checkOnTypes(null, null, bases, fpe.parse(expression), warnings); fpe.checkOnTypes(null, null, bases, fpe.parse(expression), warnings);
for (String s : warnings) { for (IssueMessage m : warnings) {
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, s); warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, m.getId(), false, m.getMessage());
} }
} catch (Exception e) { } catch (Exception e) {
if (debug) { if (debug) {

View File

@ -37,6 +37,7 @@ import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IssueMessage;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
@ -554,15 +555,15 @@ public class StructureDefinitionValidator extends BaseValidator {
// we got to the root before finding anything typed // we got to the root before finding anything typed
types.add(elements.get(0).getNamedChildValue("path")); types.add(elements.get(0).getNamedChildValue("path"));
} }
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
ValidationContext vc = new ValidationContext(invariant); ValidationContext vc = new ValidationContext(invariant);
if (Utilities.existsInList(rootPath, context.getResourceNames())) { if (Utilities.existsInList(rootPath, context.getResourceNames())) {
fpe.checkOnTypes(vc, rootPath, types, fpe.parse(exp), warnings); fpe.checkOnTypes(vc, rootPath, types, fpe.parse(exp), warnings);
} else { } else {
fpe.checkOnTypes(vc, "DomainResource", types, fpe.parse(exp), warnings); fpe.checkOnTypes(vc, "DomainResource", types, fpe.parse(exp), warnings);
} }
for (String s : warnings) { for (IssueMessage s : warnings) {
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, key+": "+s); warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, s.getId(), false, key+": "+s.getMessage());
} }
} catch (Exception e) { } catch (Exception e) {
if (debug) { if (debug) {
@ -593,11 +594,11 @@ public class StructureDefinitionValidator extends BaseValidator {
hint(errors, "2023-10-31", IssueType.INFORMATIONAL, stack, false, I18nConstants.UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV, listContexts(sd)); hint(errors, "2023-10-31", IssueType.INFORMATIONAL, stack, false, I18nConstants.UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV, listContexts(sd));
} else } else
try { try {
List<String> warnings = new ArrayList<>(); List<IssueMessage> warnings = new ArrayList<>();
ValidationContext vc = new ValidationContext(invariant); ValidationContext vc = new ValidationContext(invariant);
fpe.checkOnTypes(vc, "DomainResource", types, fpe.parse(exp), warnings); fpe.checkOnTypes(vc, "DomainResource", types, fpe.parse(exp), warnings);
for (String s : warnings) { for (IssueMessage s : warnings) {
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, s); warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, s.getId(), false, s.getMessage());
} }
} catch (Exception e) { } catch (Exception e) {
if (debug) { if (debug) {

View File

@ -20,7 +20,7 @@
<properties> <properties>
<guava_version>32.0.1-jre</guava_version> <guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version> <hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.4.15</validator_test_case_version> <validator_test_case_version>1.4.16-SNAPSHOT</validator_test_case_version>
<jackson_version>2.15.2</jackson_version> <jackson_version>2.15.2</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version> <junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version> <junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>