From c58703c63f9efcae0e45bba1ccfe0113d610bbc8 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 29 Jul 2023 08:49:27 +1000 Subject: [PATCH] Improved invariant checking --- .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 9 ++--- .../type/StructureDefinitionValidator.java | 33 ++++++++++++++----- pom.xml | 2 +- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 2277deed8..fa6ee00c4 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -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"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index c95a25b9f..a8bd0e193 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -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} 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 ab57c52f5..0e20e2636 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 @@ -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 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 errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl, StructureDefinition base) { Map invariantMap = new HashMap<>(); boolean ok = true; List 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 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) { + 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, StructureDefinition base) { boolean ok = true; boolean typeMustSupport = false; String path = element.getNamedChildValue("path"); @@ -472,13 +473,14 @@ 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, 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 errors, Element invariant, NodeStack stack, Map invariantMap, List elements, Element element, String path, String rootPath, String profileUrl, boolean snapshot) { + private boolean validateElementDefinitionInvariant(List errors, Element invariant, NodeStack stack, Map invariantMap, List 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"); diff --git a/pom.xml b/pom.xml index 0543d8883..5ede54236 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 32.0.1-jre 6.4.1 - 1.3.18 + 1.3.19-SNAPSHOT 2.15.2 5.9.2 1.8.2