Improved invariant checking

This commit is contained in:
Grahame Grieve 2023-07-29 08:49:27 +10:00
parent bd3d718256
commit c58703c63f
4 changed files with 31 additions and 14 deletions

View File

@ -946,6 +946,7 @@ public class I18nConstants {
public static final String ED_INVARIANT_DIFF_NO_SOURCE = "ED_INVARIANT_DIFF_NO_SOURCE";
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";

View File

@ -970,10 +970,11 @@ QUESTIONNAIRE_Q_ITEM_DERIVED_ANSWER_OPTIONS_NEW = The item with linkId ''{1}'' f
PRIMITIVE_MUSTHAVEVALUE_MESSAGE = The element definition ``{0}`` in the profile ''{1}'' requires that a value be present in this element
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_one = The element definition ``{0}`` in the profile ''{1}'' requires that if a value is not present, the extension ''{2}'' must be present
PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_other = The element definition ``{0}`` in the profile ''{1}'' requires that if a value is not present, one of the extensions ''{2}'' must be present
ED_INVARIANT_NO_KEY = The invariant has no key, so the content cannot be validated
ED_INVARIANT_NO_EXPRESSION = The invariant ''{0}'' has no computable expression, so validators will not be able to check it
ED_INVARIANT_EXPRESSION_CONFLICT = The invariant ''{0}'' has an expression ''{1}'', which differs from the earlier expression provided of ''{2}'' (invariants are allowed to repeat, but cannot differ)
ED_INVARIANT_EXPRESSION_ERROR = Error in invariant ''{0}'' with expression ''{1}'': {2}
ED_INVARIANT_NO_KEY = The constraint has no key, so the content cannot be validated
ED_INVARIANT_KEY_ALREADY_USED = The constraint key ''{0}'' already exists in the base profile ''{1}''
ED_INVARIANT_NO_EXPRESSION = The constraint ''{0}'' has no computable expression, so validators will not be able to check it
ED_INVARIANT_EXPRESSION_CONFLICT = The constraint ''{0}'' has an expression ''{1}'', which differs from the earlier expression provided of ''{2}'' (invariants are allowed to repeat, but cannot differ)
ED_INVARIANT_EXPRESSION_ERROR = Error in constraint ''{0}'' with expression ''{1}'': {2}
SNAPSHOT_IS_EMPTY = The snapshot for the profile ''{0}'' is empty (which should not happen)
TERMINOLOGY_TX_HINT = {1}
TERMINOLOGY_TX_WARNING = {1}

View File

@ -26,6 +26,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.Extension;
@ -131,10 +132,10 @@ public class StructureDefinitionValidator extends BaseValidator {
boolean logical = "logical".equals(src.getNamedChildValue("kind"));
boolean constraint = "constraint".equals(src.getNamedChildValue("derivation"));
for (Element differential : differentials) {
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint, src.getNamedChildValue("type"), src.getNamedChildValue("url")) && ok;
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint, src.getNamedChildValue("type"), src.getNamedChildValue("url"), base) && ok;
}
for (Element snapshotE : snapshots) {
ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint, src.getNamedChildValue("type"), src.getNamedChildValue("url")) && ok;
ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint, src.getNamedChildValue("type"), src.getNamedChildValue("url"), base) && ok;
}
// obligation profile support
@ -217,7 +218,7 @@ public class StructureDefinitionValidator extends BaseValidator {
NodeStack stack = push.push(child, c, null, null);
if (child.getName().equals("extension")) {
String url = child.getNamedChildValue("url");
if ("http://hl7.org/fhir/tools/StructureDefinition/obligation".equals(url)) {
if (Utilities.existsInList(url, ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
// this is ok, and it doesn't matter what's in the obligation
} else {
ok = false;
@ -332,19 +333,19 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl) {
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl, StructureDefinition base) {
Map<String, String> invariantMap = new HashMap<>();
boolean ok = true;
List<Element> elements = elementList.getChildrenByName("element");
int cc = 0;
for (Element element : elements) {
ok = validateElementDefinition(errors, elements, 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, base) && ok;
cc++;
}
return ok;
}
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) {
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, StructureDefinition base) {
boolean ok = true;
boolean typeMustSupport = false;
String path = element.getNamedChildValue("path");
@ -472,13 +473,14 @@ 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, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl, snapshot) && ok;
ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl, snapshot, base) && ok;
cc++;
}
return ok;
}
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 snapshot) {
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 snapshot, StructureDefinition base) {
boolean ok = true;
String key = invariant.getNamedChildValue("key");
String expression = invariant.getNamedChildValue("expression");
@ -525,13 +527,26 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
} else {
ok = rule(errors, "2023-07-27", IssueType.INVALID, stack, source == null || source.equals(profileUrl), I18nConstants.ED_INVARIANT_DIFF_NO_SOURCE, key, source);
ok = rule(errors, "2023-07-27", IssueType.INVALID, stack, source == null || source.equals(profileUrl), I18nConstants.ED_INVARIANT_DIFF_NO_SOURCE, key, source) &&
rule(errors, "2023-07-27", IssueType.INVALID, stack, !haseHasInvariant(base, key), I18nConstants.ED_INVARIANT_KEY_ALREADY_USED, key, base.getVersionedUrl());
}
}
}
return ok;
}
private boolean haseHasInvariant(StructureDefinition base, String key) {
for (ElementDefinition ed : base.getSnapshot().getElement()) {
for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
if (key.equals(inv.getKey())) {
return true;
}
}
}
return false;
}
private String tail(Element te, Element newte) {
String p = te.getNamedChildValue("path");
String pn = newte.getNamedChildValue("path");

View File

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