From 8bb908ef5c48426914f15e6f680f21f01fc1be2e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Sep 2023 17:04:23 +1000 Subject: [PATCH 01/10] fix conversion issues that surfaced in xig --- .../conv10_50/resources10_50/ConceptMap10_50.java | 2 +- .../conv30_50/resources30_50/ConceptMap30_50.java | 6 ++++++ .../conv40_50/resources40_50/ActivityDefinition40_50.java | 2 +- .../conv43_50/resources43_50/ActivityDefinition43_50.java | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_50/resources10_50/ConceptMap10_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_50/resources10_50/ConceptMap10_50.java index 6ef0e9909..ff1c017d1 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_50/resources10_50/ConceptMap10_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv10_50/resources10_50/ConceptMap10_50.java @@ -212,7 +212,7 @@ public class ConceptMap10_50 { tgt.setValue(org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship.NOTRELATEDTO); break; default: - tgt.setValue(org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship.NULL); + tgt.setValue(null); break; } return tgt; diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv30_50/resources30_50/ConceptMap30_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv30_50/resources30_50/ConceptMap30_50.java index b5a088758..626c0a948 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv30_50/resources30_50/ConceptMap30_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv30_50/resources30_50/ConceptMap30_50.java @@ -268,6 +268,9 @@ public class ConceptMap30_50 { case NOTRELATEDTO: tgt.setValue(org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence.DISJOINT); break; + case RELATEDTO: + tgt.setValue(org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence.RELATEDTO); + break; default: tgt.setValue(org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence.NULL); break; @@ -310,6 +313,9 @@ public class ConceptMap30_50 { case DISJOINT: tgt.setValue(org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship.NOTRELATEDTO); break; + case RELATEDTO: + tgt.setValue(org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship.RELATEDTO); + break; default: tgt.setValue(org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship.NULL); break; diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java index 11fe3fa2d..2c2c128bf 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/resources40_50/ActivityDefinition40_50.java @@ -295,7 +295,7 @@ public class ActivityDefinition40_50 { tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.SUPPLYREQUEST); break; case TASK: - tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.NULL); + tgt.setValue(null); tgt.addExtension(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME, new CodeType("Task")); break; case VISIONPRESCRIPTION: diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java index c97df92ad..7d7697b0a 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv43_50/resources43_50/ActivityDefinition43_50.java @@ -295,7 +295,7 @@ public class ActivityDefinition43_50 { tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.SUPPLYREQUEST); break; case TASK: - tgt.setValue(org.hl7.fhir.r5.model.ActivityDefinition.RequestResourceTypes.NULL); + tgt.setValue(null); tgt.addExtension(VersionConvertorConstants.EXT_ACTUAL_RESOURCE_NAME, new CodeType("Task")); break; case VISIONPRESCRIPTION: From c270f1761fab5cfd9f77cec8ef69b18cc87ec615 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Sep 2023 17:05:23 +1000 Subject: [PATCH 02/10] Allow suppressing snapshot errors for XIG --- .../conformance/profile/ProfilePathProcessor.java | 7 +++++-- .../r5/conformance/profile/ProfileUtilities.java | 13 ++++++++++++- .../org/hl7/fhir/r5/context/ContextUtilities.java | 12 ++---------- .../fhir/r5/renderers/ProfileDrivenRenderer.java | 5 ++++- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java index d13486393..499049837 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java @@ -32,7 +32,7 @@ import lombok.With; @AllArgsConstructor(access = AccessLevel.PRIVATE) public class ProfilePathProcessor { - + @Getter protected final ProfileUtilities profileUtilities; @@ -96,6 +96,7 @@ public class ProfilePathProcessor { @With final PathSlicingParams slicing; + private ProfilePathProcessor( ProfileUtilities profileUtilities ) { @@ -558,7 +559,9 @@ public class ProfilePathProcessor { ElementDefinition res; ElementDefinition template = null; if (diffMatches.get(0).hasType() && "Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode()) && !profileUtilities.isValidType(diffMatches.get(0).getType().get(0), currentBase)) { - throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT, getUrl(), diffMatches.get(0).getPath(), diffMatches.get(0).getType().get(0), currentBase.typeSummary())); + if (!ProfileUtilities.isSuppressIgnorableExceptions()) { + throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT, getUrl(), diffMatches.get(0).getPath(), diffMatches.get(0).getType().get(0), currentBase.typeSummary())); + } } String id = diffMatches.get(0).getId(); String lid = profileUtilities.tail(id); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 8b6cff423..91c1effd7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -136,6 +136,9 @@ import org.hl7.fhir.utilities.xml.SchematronWriter.Section; */ public class ProfileUtilities extends TranslatingUtilities { + private static boolean suppressIgnorableExceptions; + + public class ElementDefinitionCounter { int countMin = 0; int countMax = 0; @@ -2886,7 +2889,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } } - if (!ok) { + if (!ok && !isSuppressIgnorableExceptions()) { throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl())); } } @@ -4467,4 +4470,12 @@ public class ProfileUtilities extends TranslatingUtilities { return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode()); } + public static boolean isSuppressIgnorableExceptions() { + return suppressIgnorableExceptions; + } + + public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) { + ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java index 480f8061a..abbd70a18 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java @@ -42,7 +42,6 @@ public class ContextUtilities implements ProfileKnowledgeProvider { private IWorkerContext context; private boolean suppressDebugMessages; - private boolean ignoreProfileErrors; private XVerExtensionManager xverManager; private Map oidCache = new HashMap<>(); private List allStructuresList = new ArrayList(); @@ -61,14 +60,7 @@ public class ContextUtilities implements ProfileKnowledgeProvider { public void setSuppressDebugMessages(boolean suppressDebugMessages) { this.suppressDebugMessages = suppressDebugMessages; } - public boolean isIgnoreProfileErrors() { - return ignoreProfileErrors; - } - - public void setIgnoreProfileErrors(boolean ignoreProfileErrors) { - this.ignoreProfileErrors = ignoreProfileErrors; - } - + public String oid2Uri(String oid) { if (oid != null && oid.startsWith("urn:oid:")) { oid = oid.substring(8); @@ -291,7 +283,7 @@ public class ContextUtilities implements ProfileKnowledgeProvider { } pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName()); for (ValidationMessage msg : msgs) { - if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) { + if ((!ProfileUtilities.isSuppressIgnorableExceptions() && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) { if (!msg.isIgnorableError()) { throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage())); } else { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index 8b890e4c9..527099bb6 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.model.Address; import org.hl7.fhir.r5.model.Annotation; @@ -1054,7 +1055,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (pe == null) { if (ed == null) { if (url != null && url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) { - throw new DefinitionException("unknown extension "+url); + if (!ProfileUtilities.isSuppressIgnorableExceptions()) { + throw new DefinitionException("unknown extension "+url); + } } // System.out.println("unknown extension "+url); pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), null); From e081b27fd528a8223b15386f78e213923a6ccc5a Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Sep 2023 17:05:32 +1000 Subject: [PATCH 03/10] XIG convenience methods --- .../fhir/utilities/json/model/JsonObject.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java index 539035f07..5bcb966aa 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/model/JsonObject.java @@ -36,6 +36,20 @@ public class JsonObject extends JsonElement { return this; } + public JsonObject addIfNotNull(String name, JsonElement value) throws JsonException { + if (value != null) { + check(name != null, "Name is null"); + if (get(name) != null) { + check(false, "Name '"+name+"' already exists (value = "+get(name).toString()+")"); + } + JsonProperty p = new JsonProperty(name, value); + properties.add(p); + propMap.put(name, p); + } + return this; + } + + // this is used by the parser which can allow duplicates = true (for the validator). You should not otherwise use it public JsonObject addForParser(String name, JsonElement value, boolean noComma, boolean nameUnquoted, boolean valueUnquoted) throws JsonException { check(name != null, "Name is null"); @@ -54,6 +68,15 @@ public class JsonObject extends JsonElement { return add(name, value == null ? new JsonNull() : new JsonString(value)); } + public JsonObject addIfNotNull(String name, String value) throws JsonException { + check(name != null, "Name is null"); + if (value == null) { + return this; + } else { + return add(name, value == null ? new JsonNull() : new JsonString(value)); + } + } + public JsonObject add(String name, boolean value) throws JsonException { check(name != null, "Name is null"); return add(name, new JsonBoolean(value)); From 4e92a7759a355e22c4230d3ac2733673e8af83a1 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Sep 2023 17:06:24 +1000 Subject: [PATCH 04/10] fix bug overwriting source package on canonical resources --- .../java/org/hl7/fhir/r5/context/CanonicalResourceManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java index f9758b720..5600403ea 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java @@ -276,7 +276,7 @@ public class CanonicalResourceManager { } // -- 2. preparation ----------------------------------------------------------------------------- - if (cr.resource != null) { + if (cr.resource != null && cr.getPackageInfo() != null) { cr.resource.setSourcePackage(cr.getPackageInfo()); } From a061db8f01d8d6f344796a27f2d79d7631ef4c93 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Sep 2023 17:07:09 +1000 Subject: [PATCH 05/10] Fix issue not validating bundles when there are multiple profiles on entry.resource --- .../fhir/utilities/i18n/I18nConstants.java | 3 + .../src/main/resources/Messages.properties | 8 +- .../hl7/fhir/validation/BaseValidator.java | 40 +- .../instance/InstanceValidator.java | 474 ++++++++++++------ .../instance/type/BundleValidator.java | 74 +-- .../instance/type/ConceptMapValidator.java | 12 +- .../instance/type/QuestionnaireValidator.java | 8 +- .../type/StructureDefinitionValidator.java | 54 +- .../instance/type/ValueSetValidator.java | 4 +- .../validation/profile/ProfileValidator.java | 6 +- .../3.0.2/iso3166.cache | 29 +- .../org.hl7.fhir.validation/3.0.2/loinc.cache | 49 +- .../4.0.1/iso3166.cache | 22 + .../org.hl7.fhir.validation/4.0.1/lang.cache | 73 ++- .../org.hl7.fhir.validation/4.0.1/loinc.cache | 117 ++++- .../4.0.1/snomed.cache | 65 +++ ...tats.un.org_unsd_methods_m49_m49.htm.cache | 22 +- .../org.hl7.fhir.validation/5.0.0/loinc.cache | 94 +++- pom.xml | 2 +- 19 files changed, 834 insertions(+), 322 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 3a0e9128c..d036ce394 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 @@ -989,6 +989,9 @@ public class I18nConstants { public static final String CONCEPTMAP_VS_INVALID_CONCEPT_CODE = "CONCEPTMAP_VS_INVALID_CONCEPT_CODE"; public static final String CONCEPTMAP_VS_INVALID_CONCEPT_CODE_VER = "CONCEPTMAP_VS_INVALID_CONCEPT_CODE_VER"; public static final String VALUESET_INC_TOO_MANY_CODES = "VALUESET_INC_TOO_MANY_CODES"; + public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH"; + public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES"; + public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index f46b4a0cf..aae798ed4 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -653,9 +653,6 @@ SD_MUST_HAVE_DERIVATION = StructureDefinition {0} must have a derivation, since VALIDATION_VAL_PROFILE_OTHER_VERSION = Profile is for a different version of FHIR ({0}) so has been ignored VALIDATION_VAL_PROFILE_THIS_VERSION_OK = Profile for this version of FHIR - all OK VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = Profile is for this version of FHIR, but is an invalid type {0} -#The following error cannot occur for a single item. _one case left intentionally blank. -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_one = -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_other = {0} profiles found for {1} resource. More than one is not supported at this time. (Type {2}: {3}) RENDER_BUNDLE_HEADER_ROOT = Bundle {0} of type {1} RENDER_BUNDLE_HEADER_ENTRY = Entry {0} RENDER_BUNDLE_HEADER_ENTRY_URL = Entry {0} - fullUrl = {1} @@ -1049,3 +1046,8 @@ CONCEPTMAP_VS_TOO_MANY_CODES = The concept map has too many codes to validate ({ CONCEPTMAP_VS_INVALID_CONCEPT_CODE = The code ''{1}'' in the system {0} is not valid in the value set ''{2}'' CONCEPTMAP_VS_INVALID_CONCEPT_CODE_VER = The code ''{2}'' in the system {0} version {1} is not valid in the value set ''{3}'' VALUESET_INC_TOO_MANY_CODES = The value set include has too many codes to validate ({0}) +BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH = The {1} resource did not match any of the allowed profiles (Type {2}: {3}) +BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES = The {1} resource matched more than one of the allowed profiles ({3}) +BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON = The {1} resource did not math the profile {2} because: {3} + + diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index 9e53ec9f1..8662bd357 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -80,27 +80,29 @@ import org.hl7.fhir.validation.instance.utils.NodeStack; public class BaseValidator implements IValidationContextResourceLoader { - public class BooleanValue { - private boolean value; + public class BooleanHolder { + private boolean value = true; - public BooleanValue(boolean value) { + public BooleanHolder() { + super(); + this.value = true; + } + public BooleanHolder(boolean value) { super(); this.value = value; } - - public boolean isValue() { + public void fail() { + value = false; + } + public boolean ok() { return value; } - - public void setValue(boolean value) { - this.value = value; - } - public void see(boolean ok) { value = value && ok; } } + public class TrackedLocationRelatedMessage { private Object location; @@ -642,7 +644,7 @@ public class BaseValidator implements IValidationContextResourceLoader { * Set this parameter to false if the validation does not pass * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ - protected void txIssue(List errors, String ruleDate, String txLink, int line, int col, String path, OperationOutcomeIssueComponent issue) { + protected ValidationMessage txIssue(List errors, String ruleDate, String txLink, int line, int col, String path, OperationOutcomeIssueComponent issue) { IssueType code = IssueType.fromCode(issue.getCode().toCode()); IssueSeverity severity = IssueSeverity.fromCode(issue.getSeverity().toCode()); ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, code, line, col, path, issue.getDetails().getText(), severity).setTxLink(txLink); @@ -651,6 +653,7 @@ public class BaseValidator implements IValidationContextResourceLoader { // } // } // return thePass; + return vmsg; } /** @@ -1002,7 +1005,7 @@ public class BaseValidator implements IValidationContextResourceLoader { } - protected IndexedElement getFromBundle(Element bundle, String ref, String fullUrl, List errors, String path, String type, boolean isTransaction) { + protected IndexedElement getFromBundle(Element bundle, String ref, String fullUrl, List errors, String path, String type, boolean isTransaction, BooleanHolder bh) { String targetUrl = null; String version = ""; String resourceType = null; @@ -1086,7 +1089,7 @@ public class BaseValidator implements IValidationContextResourceLoader { } if (match != null && resourceType != null) - rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, match.getType().equals(resourceType), I18nConstants.REFERENCE_REF_RESOURCETYPE, ref, match.getType()); + bh.see(rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, match.getType().equals(resourceType), I18nConstants.REFERENCE_REF_RESOURCETYPE, ref, match.getType())); if (match == null) { warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, !ref.startsWith("urn"), I18nConstants.BUNDLE_BUNDLE_NOT_LOCAL, ref); if (!Utilities.isAbsoluteUrl(ref)) { @@ -1324,7 +1327,8 @@ public class BaseValidator implements IValidationContextResourceLoader { } - protected void checkDefinitionStatus(List errors, Element element, String path, StructureDefinition ex, CanonicalResource source, String type) { + protected boolean checkDefinitionStatus(List errors, Element element, String path, StructureDefinition ex, CanonicalResource source, String type) { + boolean ok = true; String vurl = ex.getVersionedUrl(); StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(ex); @@ -1358,6 +1362,14 @@ public class BaseValidator implements IValidationContextResourceLoader { } } } + return ok; + } + + protected boolean check(boolean ok) { +// if (!ok) { +// System.out.println("notok"); // #FIXME +// } + return ok; } } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index f8426d395..eb7d0b23d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -233,6 +233,7 @@ import org.w3c.dom.Document; public class InstanceValidator extends BaseValidator implements IResourceValidator { + public class StructureDefinitionSorterByUrl implements Comparator { @Override @@ -433,7 +434,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!ok && !record.isEmpty()) { ctxt.sliceNotes(url, record); } - return ok; + return check(ok); } @Override @@ -722,7 +723,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (logicals.size() > 0) { if (rulePlural(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, "Configuration", logicals.size() == 1, logicals.size(), I18nConstants.MULTIPLE_LOGICAL_MODELS, ResourceUtilities.listUrls(logicals))) { parser.setLogical(logicals.get(0)); - } + } } if (parser instanceof XmlParser) { ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation); @@ -1015,6 +1016,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = rule(errorsFixed, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, false, I18nConstants.PATTERN_CHECK_STRING, fixedLine.getValue(), fixedSource, allErrorsFixed) && ok; } } + } else { + ok = false; } } else if (!pattern) { lineSizeCheck = lines.size() == fixed.getLine().size(); @@ -1023,9 +1026,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat for (int i = 0; i < lines.size(); i++) { ok = checkFixedValue(errors, path + ".line", lines.get(i), fixed.getLine().get(i), fixedSource, "line", focus, pattern) && ok; } + } else { + ok = false; } } - return ok; + return check(ok); } private boolean checkAttachment(List errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern) { @@ -1038,7 +1043,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), fixedSource, "hash", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), fixedSource, "title", focus, pattern) && ok; - return ok; + return check(ok); } // public API @@ -1098,9 +1103,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_INVALID, system); return false; } else { + boolean ok = true; try { if (context.fetchResourceWithException(ValueSet.class, system, element.getProperty().getStructure()) != null) { - rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET, system); + ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET, system) && ok; // Lloyd: This error used to prohibit checking for downstream issues, but there are some cases where that checking needs to occur. Please talk to me before changing the code back. } boolean done = false; @@ -1118,13 +1124,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system); } else { if (hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, cs.getContent() != CodeSystemContentMode.NOTPRESENT, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOT_USABLE, system)) { - rule(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, false, "Error - this should not happen? (Consult GG)"); + ok = rule(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, false, "Error - this should not happen? (Consult GG)") && ok; } } } - return true; + return check(ok); } catch (Exception e) { - return true; + return check(ok); } } } @@ -1215,7 +1221,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat List errorsFixed; for (int j = 0; j < codings.size() && !found; ++j) { errorsFixed = new ArrayList<>(); - ok = checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern) && ok; + checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern); if (!hasErrors(errorsFixed)) { found = true; } else { @@ -1264,11 +1270,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return ok; + return check(ok); } - private boolean checkCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack) { - boolean res = true; + private boolean checkCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, BooleanHolder bh) { + boolean checkDisp = true; if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) { ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) { @@ -1278,16 +1284,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); + } else { + bh.fail(); } } else { try { CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element); if (!cc.hasCoding()) { if (binding.getStrength() == BindingStrength.REQUIRED) - rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeReference(binding.getValueSet(), valueset)); + bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeReference(binding.getValueSet(), valueset))); else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) - rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)), valueset.getVersionedUrl()); + bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)), valueset.getVersionedUrl())); else warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(binding.getValueSet(), valueset)); } @@ -1315,7 +1323,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ValidationResult vr = checkCodeOnServer(stack, valueset, cc, true); // we're going to validate the codings directly, so only check the valueset if (vr != null && vr.isOk()) { for (OperationOutcomeIssueComponent iss : vr.getIssues()) { - txIssue(errors, "2023-08-19", vr.getTxLink(), element.line(), element.col(), path, iss); + var vmsg = txIssue(errors, "2023-08-19", vr.getTxLink(), element.line(), element.col(), path, iss); + bh.see(!vmsg.isError()); } } if (!vr.isOk()) { @@ -1331,7 +1340,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) - checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack); + bh.see(checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack)); else if (!noExtensibleWarnings) txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); } else if (binding.getStrength() == BindingStrength.PREFERRED) { @@ -1341,10 +1350,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } else { if (binding.getStrength() == BindingStrength.REQUIRED) { - txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc)); + bh.see(txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc))); } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) - checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack); + bh.see(checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack)); if (!noExtensibleWarnings) txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc)); } else if (binding.getStrength() == BindingStrength.PREFERRED) { @@ -1357,21 +1366,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (vr.getSeverity() == IssueSeverity.INFORMATION) { txHint(errors, "2023-07-03", vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } else { - res = false; + checkDisp = false; txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } } else { if (binding.getStrength() == BindingStrength.EXTENSIBLE) { removeTrackedMessagesForLocation(errors, element, path); } - res = false; + checkDisp = false; } } // Then, for any codes that are in code systems we are able // to validate, we'll validate that the codes actually exist if (bindingsOk) { for (Coding nextCoding : cc.getCoding()) { - checkBindings(errors, path, element, stack, valueset, nextCoding); + bh.see(checkBindings(errors, path, element, stack, valueset, nextCoding)); } } timeTracker.tx(t, "vc "+cc.toString()); @@ -1389,10 +1398,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return res; + return checkDisp; } - public void checkBindings(List errors, String path, Element element, NodeStack stack, ValueSet valueset, Coding nextCoding) { + public boolean checkBindings(List errors, String path, Element element, NodeStack stack, ValueSet valueset, Coding nextCoding) { + boolean ok = true; if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem())) { ValidationResult vr = checkCodeOnServer(stack, valueset, nextCoding, false); if (vr != null && vr.isOk()) { @@ -1406,15 +1416,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (vr.getSeverity() == IssueSeverity.WARNING) { txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } else { - txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()) && ok; } } } + return check(ok); } private boolean checkTerminologyCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, StructureDefinition logical) { - boolean res = true; + boolean ok = true; if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) { ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) { @@ -1424,16 +1435,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat CodeSystem cs = context.fetchCodeSystem(binding.getValueSet()); if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) { warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); + } else { + ok = false; } } else { try { CodeableConcept cc = convertToCodeableConcept(element, logical); if (!cc.hasCoding()) { if (binding.getStrength() == BindingStrength.REQUIRED) - rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getVersionedUrl()); + ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getVersionedUrl()) && ok; else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) - rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)), valueset.getVersionedUrl()); + ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)), valueset.getVersionedUrl()) && ok; else warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(binding.getValueSet(), valueset)); } @@ -1472,7 +1485,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) - checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack); + ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack) && ok; else if (!noExtensibleWarnings) txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString()); } else if (binding.getStrength() == BindingStrength.PREFERRED) { @@ -1482,10 +1495,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } else { if (binding.getStrength() == BindingStrength.REQUIRED) - txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(binding.getValueSet()), valueset, ccSummary(cc)); + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(binding.getValueSet()), valueset, ccSummary(cc)) && ok; else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) - checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack); + ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack) && ok; if (!noExtensibleWarnings) txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc)); } else if (binding.getStrength() == BindingStrength.PREFERRED) { @@ -1495,14 +1508,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } else if (vr.getMessage() != null) { - res = false; if (vr.getSeverity() == IssueSeverity.INFORMATION) { txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } else { txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } } else { - res = false; + ok = true; } } // Then, for any codes that are in code systems we are able @@ -1534,7 +1546,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding. if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) { - checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical); + ok = checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical) && ok; } } } else if (binding.hasValueSet()) { @@ -1544,7 +1556,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return res; + return check(ok); } private boolean checkTerminologyCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) { @@ -1644,7 +1656,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return ok; + return check(ok); } private CodeableConcept convertToCodeableConcept(Element element, StructureDefinition logical) { @@ -1718,12 +1730,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return res; } - private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) { + private boolean checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) { + boolean ok = true; ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile); if (valueset == null) { CodeSystem cs = context.fetchCodeSystem(maxVSUrl); if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) { warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl)); + } else { + ok = false; } } else { try { @@ -1739,13 +1754,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_7, describeReference(maxVSUrl, valueset), vr.getMessage()); else - txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset), ccSummary(cc)); + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset), ccSummary(cc)) && ok; } } catch (Exception e) { if (STACK_TRACE) e.printStackTrace(); warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); } } + return check(ok); } // private String describeValueSet(String url) { @@ -1789,7 +1805,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); } } - return ok; + return check(ok); } private boolean checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) { @@ -1824,7 +1840,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); } } - return ok; + return check(ok); } private String ccSummary(CodeableConcept cc) { @@ -1841,7 +1857,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) { @@ -1951,7 +1967,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return ok; + return check(ok); } private boolean isValueSet(String url) { @@ -1969,10 +1985,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern) && ok; - return ok; + return check(ok); } - private StructureDefinition checkExtension(ValidatorHostContext hostContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException { + private boolean checkExtension(ValidatorHostContext hostContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException { + boolean ok = true; String url = element.getNamedChildValue("url"); String u = url.contains("|") ? url.substring(0, url.indexOf("|")) : url; boolean isModifier = element.getName().equals("modifierExtension"); @@ -1989,15 +2006,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (ex == null) { warning(errors, "2022-12-17", IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_NO_MATCH); } else { - rule(errors, "2022-12-17", IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_IGNORE); + ok = rule(errors, "2022-12-17", IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_IGNORE) && ok; } } else { if (url.equals(ex.getUrl())) { warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_MISLEADING); } else if (url.equals(ex.getVersionedUrl())) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_NOT_ALLOWED); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_NOT_ALLOWED) && ok; } else { // - rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_REVERSION, ex.getVersion()); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_REVERSION, ex.getVersion()) && ok; } } } @@ -2006,7 +2023,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (ex == null) { if (extensionUrl != null && !isAbsolute(url)) { if (extensionUrl.equals(profile.getUrl())) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), I18nConstants.EXTENSION_EXT_SUBEXTENSION_INVALID, url, profile.getVersionedUrl()); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), I18nConstants.EXTENSION_EXT_SUBEXTENSION_INVALID, url, profile.getVersionedUrl()) && ok; } } else if (SpecialExtensions.isKnownExtension(url)) { ex = SpecialExtensions.getDefinition(url); @@ -2014,41 +2031,43 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // nothing } else if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN_NOTHERE, url)) { hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN, url); + } else { + ok = false; } } if (ex != null) { trackUsage(ex, hostContext, element); // check internal definitions are coherent if (isModifier) { - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY) && ok; } else { - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN) && ok; } // 1. can this extension be used here? - checkExtensionContext(hostContext.getAppContext(), errors, resource, container, ex, containerStack, hostContext, isModifier); - checkDefinitionStatus(errors, element, path, ex, profile, context.formatMessage(I18nConstants.MSG_DEPENDS_ON_EXTENSION)); + ok = checkExtensionContext(hostContext.getAppContext(), errors, resource, container, ex, containerStack, hostContext, isModifier) && ok; + ok = checkDefinitionStatus(errors, element, path, ex, profile, context.formatMessage(I18nConstants.MSG_DEPENDS_ON_EXTENSION)) && ok; if (isModifier) - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_Y, url); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_Y, url) && ok; else - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_N, url); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_N, url) && ok; // check the type of the extension: Set allowedTypes = listExtensionTypes(ex); String actualType = getExtensionType(element); if (actualType != null) - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), I18nConstants.EXTENSION_EXT_TYPE, url, allowedTypes.toString(), actualType); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), I18nConstants.EXTENSION_EXT_TYPE, url, allowedTypes.toString(), actualType) && ok; else if (element.hasChildren("extension")) - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_WRONG, url); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_WRONG, url) && ok; else - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_ABSENT, url); + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_ABSENT, url) && ok; // 3. is the content of the extension valid? - validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url, pct, mode); + ok = validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url, pct, mode) && ok; } - return ex; + return check(ok); } private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) { @@ -2132,7 +2151,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = true; } else if (en.equals("CanonicalResource") && VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(stack.getLiteralPath())) { ok = true; - } else if (plist.contains(en) && pu == null) { + } else if (hasElementName(plist, en) && pu == null) { ok = true; } @@ -2221,6 +2240,40 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } + private boolean hasElementName(List plist, String en) { + String[] ep = en.split("\\."); + for (String s : plist) { + if (s.equals(en)) { + return true; + } + String[] sp = s.split("\\."); + int si = 0; + int ei = 0; + boolean mismatch = false; + while (si < sp.length && ei < ep.length) { + var ps = sp[si]; + var pe = ep[ei]; + if (!ps.equals(pe)) { + if (pe.endsWith("[x]")) { + if (ps.equals(pe.substring(0, pe.length()-3)) && si < sp.length - 1 && sp[si+1].startsWith("ofType(")) { + si++; + } else { + mismatch = true; + } + } else { + mismatch = true; + } + } + si++; + ei++; + } + if (!mismatch && si == sp.length && ei == ep.length) { + return true; + } + } + return false; + } + private boolean checkConformsToProfile(Object appContext, List errors, Element resource, Element container, NodeStack stack, String extUrl, String expression, String pu) { if (pu == null) { return true; @@ -2243,7 +2296,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat record.add(v); } } - return ok; + return check(ok); } else { warning(errors, "2023-07-03", IssueType.UNKNOWN, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.EXTENSION_CONTEXT_UNABLE_TO_CHECK_PROFILE, extUrl, expression, pu); @@ -2334,9 +2387,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat else if (fixed instanceof org.hl7.fhir.r5.model.UuidType) ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UuidType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.UuidType) fixed).getValue()); else if (fixed instanceof org.hl7.fhir.r5.model.IdType) - ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IdType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.IdType) fixed).getValue()); + ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IdType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.IdType) fixed).getValue()); else if (fixed instanceof Quantity) - checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern); + ok = checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern) && ok; else if (fixed instanceof Address) ok = checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern); else if (fixed instanceof ContactPoint) @@ -2374,11 +2427,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat Element ex = getExtensionByUrl(extensions, e.getUrl()); if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) { ok = checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"), false) && ok; + } else { + ok = false; } } + } else { + ok = false; } } - return ok; + return check(ok); } private boolean checkHumanName(List errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) { @@ -2393,6 +2450,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_FAMILY, (fixed.hasFamily() ? "1" : "0"), Integer.toString(parts.size()))) { for (int i = 0; i < parts.size(); i++) ok = checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern) && ok; + } else { + ok = false; } } if (!pattern || fixed.hasGiven()) { @@ -2400,6 +2459,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_GIVEN, Integer.toString(fixed.getGiven().size()), Integer.toString(parts.size()))) { for (int i = 0; i < parts.size(); i++) ok = checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern) && ok; + } else { + ok = false; } } if (!pattern || fixed.hasPrefix()) { @@ -2407,6 +2468,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_PREFIX, Integer.toString(fixed.getPrefix().size()), Integer.toString(parts.size()))) { for (int i = 0; i < parts.size(); i++) ok = checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern) && ok; + } else { + ok = false; } } if (!pattern || fixed.hasSuffix()) { @@ -2414,9 +2477,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_SUFFIX, Integer.toString(fixed.getSuffix().size()), Integer.toString(parts.size()))) { for (int i = 0; i < parts.size(); i++) ok = checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern) && ok; + } else { + ok = false; } } - return ok; + return check(ok); } private boolean checkIdentifier(List errors, String path, Element element, ElementDefinition context) { @@ -2427,7 +2492,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String value = element.getNamedChildValue("value"); ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, value == null || isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE, value) && ok; } - return ok; + return check(ok); } private boolean checkIdentifier(List errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) { @@ -2438,14 +2503,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkPeriod(List errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) { boolean ok = true; ok = checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkPrimitive(ValidatorHostContext hostContext, List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException { @@ -2470,7 +2535,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } ok = rulePlural(errors, "2023-06-18", IssueType.INVALID, e.line(), e.col(), path, found, context.getValueAlternatives().size(), I18nConstants.PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE, context.getId(), profile.getVersionedUrl(), b.toString()) && ok; } - return ok; + return check(ok); } else { boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue()); if (hasBiDiControls) { @@ -2743,9 +2808,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat i++; } if (i < raw.length()-1 ) { - ok = warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw.subSequence(i, i+2)) && ok; + if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw.subSequence(i, i+2))) { + ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok; + } } else { - ok = warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw) && ok; + if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw)) { + ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok; + } } } } @@ -2756,7 +2825,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String ns = xhtml.getNsDecl(); ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS) && ok; // check that inner namespaces are all correct - checkInnerNS(errors, e, path, xhtml.getChildNodes()); + ok = checkInnerNS(errors, e, path, xhtml.getChildNodes()) && ok; ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, xhtml.getName()) && ok; // check that no illegal elements and attributes have been used ok = checkInnerNames(errors, e, path, xhtml.getChildNodes(), false) && ok; @@ -2786,7 +2855,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // for nothing to check - return ok; + return check(ok); } private String getRegexFromType(String fhirType) { @@ -2903,6 +2972,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rp == ReferenceValidationPolicy.CHECK_VALID) { // todo.... } + } else { + ok = false; } } catch (Exception ex) { // won't happen @@ -2911,7 +2982,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return ok; + return check(ok); } private Set listExpectedCanonicalTypes(ElementDefinition context) { @@ -3025,7 +3096,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (charCount > 0 && charCount % 4 != 0) { ok = false; } - return ok; + return check(ok); } private boolean base64HasWhitespace(String theEncoded) { @@ -3069,15 +3140,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat boolean ok = true; for (XhtmlNode node : list) { if (node.getNodeType() == NodeType.Comment) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL) && ok; } if (node.getNodeType() == NodeType.Element) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, HTML_ELEMENTS.contains(node.getName()), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, HTML_ELEMENTS.contains(node.getName()), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()) && ok; for (String an : node.getAttributes().keySet()) { boolean bok = an.startsWith("xmlns") || HTML_ATTRIBUTES.contains(an) || HTML_COMBO_LIST.contains(node.getName() + "." + an); if (!bok) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName()); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName()) && ok; } } @@ -3086,7 +3157,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())) && ok; } } - return ok; + return check(ok); } private boolean checkUrls(List errors, Element e, String path, List list) { @@ -3103,7 +3174,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkUrls(errors, e, path, node.getChildNodes()) && ok; } } - return ok; + return check(ok); } private String checkValidUrl(String value) { @@ -3156,14 +3227,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return null; } - private void checkInnerNS(List errors, Element e, String path, List list) { + private boolean checkInnerNS(List errors, Element e, String path, List list) { + boolean ok = true; for (XhtmlNode node : list) { if (node.getNodeType() == NodeType.Element) { String ns = node.getNsDecl(); - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS) && ok; checkInnerNS(errors, e, path, node.getChildNodes()); } } + return check(ok); } private boolean checkPrimitiveBinding(ValidatorHostContext hostContext, List errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) { @@ -3242,7 +3315,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (!noBindingMsgSuppressed) { hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2); } - return ok; + return check(ok); } private boolean isOkExtension(String value, ValueSet vs) { @@ -3252,12 +3325,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return false; } - private void checkQuantity(List errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) { - checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); - checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern); - checkFixedValue(errors, path + ".unit", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "unit", focus, pattern); - checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern); - checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern); + private boolean checkQuantity(List errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) { + boolean ok = true; + ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok; + ok = checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern) && ok; + ok = checkFixedValue(errors, path + ".unit", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "unit", focus, pattern) && ok; + ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern) && ok; + ok = checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern) && ok; + return ok; } private boolean checkQuantity(List errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) { @@ -3357,7 +3432,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return ok; + return check(ok); } private Decimal convertUcumValue(String value, String code, String minCode) { @@ -3425,6 +3500,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, Utilities.isLong(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz)) { size = Long.parseLong(sz); ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz) && ok; + } else { + ok = false; } } else if (element.hasChild("url")) { String url = element.getChildValue("url"); @@ -3455,7 +3532,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT); - return ok; + return check(ok); } // implementation @@ -3464,14 +3541,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat boolean ok = true; ok = checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkRatio(List errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) { boolean ok = true; ok = checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkReference(ValidatorHostContext hostContext, @@ -3500,7 +3577,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref); - ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element); + BooleanHolder bh = new BooleanHolder(); + ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element, bh); + ok = bh.ok() && ok; String refType; if (ref.startsWith("#")) { refType = "contained"; @@ -3745,7 +3824,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // todo: if the content is a resource, check that Reference.type is describing a resource - return ok; + return check(ok); } private boolean isSuspiciousReference(String url) { @@ -3877,7 +3956,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkReference(List errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern) { @@ -3886,7 +3965,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getTypeElement(), fixedSource, "type", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier"), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern) && ok; - return ok; + return check(ok); } private boolean checkTiming(List errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) { @@ -3898,8 +3977,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(), I18nConstants.BUNDLE_MSG_EVENT_COUNT, Integer.toString(fixed.getEvent().size()), Integer.toString(events.size()))) { for (int i = 0; i < events.size(); i++) ok = checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern) && ok; + } else { + ok = false; } - return ok; + return check(ok); } private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) { @@ -4108,7 +4189,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return null; } - private Element getValueForDiscriminator(Object appContext, List errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException { + private Element getValueForDiscriminator(Object appContext, List errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack, BooleanHolder bh) throws FHIRException, IOException { String p = stack.getLiteralPath() + "." + element.getName(); Element focus = element; String[] dlist = discriminator.split("\\."); @@ -4118,7 +4199,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (Utilities.noString(url)) throw new FHIRException(context.formatMessage(I18nConstants.NO_REFERENCE_RESOLVING_DISCRIMINATOR__FROM_, discriminator, element.getProperty().getName())); // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough? - Element target = resolve(appContext, url, stack, errors, p); + Element target = resolve(appContext, url, stack, errors, p, bh); if (target == null) throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_, url, d, discriminator, element.getProperty().getName())); focus = target; @@ -4264,7 +4345,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return true; } - private ResolvedReference localResolve(String ref, NodeStack stack, List errors, String path, Element rootResource, Element groupingResource, Element source) { + private ResolvedReference localResolve(String ref, NodeStack stack, List errors, String path, Element rootResource, Element groupingResource, Element source, BooleanHolder bh) { if (ref.startsWith("#")) { // work back through the parent list. // really, there should only be one level for this (contained resources cannot contain @@ -4329,12 +4410,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String type = stack.getParent().getParent().getElement().getChildValue(TYPE); fullUrl = stack.getParent().getElement().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary if (fullUrl == null) - rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack.getParent().getElement().line(), stack.getParent().getElement().col(), stack.getParent().getLiteralPath(), - Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL); + bh.see(rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack.getParent().getElement().line(), stack.getParent().getElement().col(), stack.getParent().getLiteralPath(), + Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL)); } if (BUNDLE.equals(stack.getElement().getType())) { String type = stack.getElement().getChildValue(TYPE); - IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type)); + IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type), bh); if (res == null) { return null; } else { @@ -4368,7 +4449,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String type = groupingResource.getChildValue(TYPE); Element entry = getEntryForSource(groupingResource, source); fullUrl = entry.getChildValue(FULL_URL); - IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type)); + IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type), bh); if (res == null) { return null; } else { @@ -4444,8 +4525,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } - private Element resolve(Object appContext, String ref, NodeStack stack, List errors, String path) throws IOException, FHIRException { - Element local = localResolve(ref, stack, errors, path, null, null, null).getFocus(); + private Element resolve(Object appContext, String ref, NodeStack stack, List errors, String path, BooleanHolder bh) throws IOException, FHIRException { + Element local = localResolve(ref, stack, errors, path, null, null, null, bh).getFocus(); if (local != null) return local; if (fetcher == null) @@ -4690,17 +4771,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException { String msg; - boolean ok; + boolean pass; try { long t = System.nanoTime(); - ok = fpe.evaluateToBoolean(hostContext.forProfile(profile), hostContext.getResource(), hostContext.getRootResource(), element, n); + pass = fpe.evaluateToBoolean(hostContext.forProfile(profile), hostContext.getResource(), hostContext.getRootResource(), element, n); timeTracker.fpe(t); msg = fpe.forLog(); } catch (Exception ex) { if (STACK_TRACE) ex.printStackTrace(); throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_EVALUATING_SLICING_EXPRESSION_FOR_ELEMENT_IN_PROFILE__PATH__FHIRPATH___, profile.getVersionedUrl(), path, n, ex.getMessage())); } - return ok; + return pass; } private void buildPattternExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException { @@ -4984,7 +5065,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // checkSpecials = we're only going to run these tests if we are actually validating this content (as opposed to we looked it up) private boolean start(ValidatorHostContext hostContext, List errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, PercentageTracker pct, ValidationMode mode) throws FHIRException { - boolean ok = true; + boolean ok = !hasErrors(errors); checkLang(resource, stack); if (crumbTrails) { @@ -5123,7 +5204,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } // System.out.println("start: "+(System.currentTimeMillis()-st)+" ("+resource.fhirType()+")"); - return ok; + return check(ok); } private StructureDefinition lookupProfileReference(List errors, Element element, NodeStack stack, @@ -5218,17 +5299,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat public boolean startInner(ValidatorHostContext hostContext, List errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode) { // the first piece of business is to see if we've validated this resource against this profile before. // if we have (*or if we still are*), then we'll just return our existing errors - boolean ok = false; + boolean ok = true; ResourceValidationTracker resTracker = getResourceTracker(element); List cachedErrors = resTracker.getOutcomes(defn); if (cachedErrors != null) { for (ValidationMessage vm : cachedErrors) { if (!errors.contains(vm)) { errors.add(vm); - ok = ok && vm.getLevel() != IssueSeverity.ERROR && vm.getLevel() != IssueSeverity.FATAL; + ok = vm.getLevel() != IssueSeverity.ERROR && vm.getLevel() != IssueSeverity.FATAL && ok; } } - return ok; + return check(ok); } if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getVersionedUrl())) { List localErrors = new ArrayList(); @@ -5248,7 +5329,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkSpecials(hostContext, errors, element, stack, checkSpecials, pct, mode) && ok; ok = validateResourceRules(errors, element, stack) && ok; } - return ok; + return check(ok); } public boolean checkSpecials(ValidatorHostContext hostContext, List errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode) { @@ -5380,7 +5461,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat i++; } } - return ok; + return check(ok); } private boolean validateCapabilityStatement(List errors, Element cs, NodeStack stack) { @@ -5406,7 +5487,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } iRest++; } - return ok; + return check(ok); } private boolean validateContains(ValidatorHostContext hostContext, List errors, String path, @@ -5430,7 +5511,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat hostContext, context.fhirType(), context.getId(), special, path, parentProfile.getUrl()); if (containedValidationPolicy.ignore()) { - return ok; + return check(ok); } String resourceName = element.getType(); @@ -5504,12 +5585,40 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } else { + // it has to conform to one of them + // iterate them in order, if none of them are valid, then report errors CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - for (CanonicalType u : typeForResource.getProfile()) { + CommaSeparatedStringBuilder bm = new CommaSeparatedStringBuilder(); + List> errorsList = new ArrayList<>(); + int matched = 0; + for (CanonicalType u : typeForResource.getProfile()) { b.append(u.asStringValue()); + long t = System.nanoTime(); + StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, u.asStringValue()); + timeTracker.sd(t); + if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), + profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), u.asStringValue())) { + trackUsage(profile, hostContext, element); + List perrors = new ArrayList<>(); + errorsList.add(perrors); + if (validateResource(hc, perrors, resource, element, profile, idstatus, stack, pct, mode)) { + bm.append(u.asStringValue()); + matched++; + } + } else { + ok = false; + } + } + if (rule(errors, "2023-09-07", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), + matched > 0, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH, "", special.toHuman(), typeForResource.getCode(), b.toString())) { + hint(errors, "2023-09-07", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), matched == 1, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES, "", special.toHuman(), typeForResource.getCode(), bm.toString()); + } else { + ok = false; + for (int i = 0; i < typeForResource.getProfile().size(); i++) { + hint(errors, "2023-09-07", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), + matched > 0, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON, "", special.toHuman(), typeForResource.getProfile().get(i).asStringValue(), summariseErrors(errorsList.get(i))); + } } - ok = rulePlural(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), - false, typeForResource.getProfile().size(), I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, special.toHuman(), typeForResource.getCode(), b.toString()) && ok; } } } else { @@ -5530,7 +5639,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return ok; + return check(ok); + } + + private String summariseErrors(List list) { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", ", " and "); + for (ValidationMessage vm : list ) { + if (vm.isError()) { + b.append(vm.getMessage()); + } + } + return b.toString(); } private boolean isValidResourceType(String type, TypeRefComponent def) { @@ -5568,13 +5687,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean validateElement(ValidatorHostContext hostContext, List errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException { - + boolean ok = true; + pct.seeElement(element); String id = element.getChildValue("id"); if (!Utilities.noString(id)) { if (stack.getIds().containsKey(id) && stack.getIds().get(id) != element) { - rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id); + ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id) && ok; } if (!stack.isResetPoint()) { stack.getIds().put(id, element); @@ -5585,7 +5705,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat stack.resetIds(); } ValidationInfo vi = element.addDefinition(profile, definition, mode); - boolean ok = true; // check type invariants ok = checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false) & ok; @@ -5616,7 +5735,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } List children = listChildren(element, stack); - List problematicPaths = assignChildren(hostContext, errors, profile, resource, stack, childDefinitions, children); + BooleanHolder bh = new BooleanHolder(); + List problematicPaths = assignChildren(hostContext, errors, profile, resource, stack, childDefinitions, children, bh); + ok = bh.ok() && ok; ok = checkCardinalities(errors, profile, element, stack, childDefinitions, children, problematicPaths) && ok; // 4. check order if any slices are ordered. (todo) @@ -5626,7 +5747,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, pct, mode) && ok; } vi.setValid(ok); - return ok; + return check(ok); } private SourcedChildDefinitions mergeChildLists(SourcedChildDefinitions source, SourcedChildDefinitions additional, String masterPath, String typePath) { @@ -5683,7 +5804,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } ok = checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true, pct, mode) && ok; } - return ok; + return check(ok); } private String time() { @@ -5828,15 +5949,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (type.equals("Identifier")) { - ok = checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn); + ok = checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn) && ok; } else if (type.equals("Coding")) { - ok = checkCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack); + ok = checkCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack) && ok; } else if (type.equals("Quantity")) { - ok = checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack); + ok = checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack) && ok; } else if (type.equals("Attachment")) { - ok = checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack); + ok = checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack) && ok; } else if (type.equals("CodeableConcept")) { - ok = checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack); + BooleanHolder bh = new BooleanHolder(); + checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack, bh); + ok = bh.ok() & ok; thisIsCodeableConcept = true; } else if (type.equals("Reference")) { ok = checkReference(hostContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, actualType, localStack, pct, mode) && ok; @@ -5848,13 +5971,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat thisExtension = url; if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), !Utilities.noString(url), I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) { if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), (extensionUrl != null) || Utilities.isAbsoluteUrl(url), I18nConstants.EXTENSION_EXT_URL_ABSOLUTE)) { - checkExtension(hostContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl, pct, mode); + ok = checkExtension(hostContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl, pct, mode) && ok; } else { ok = false; } } else { ok = false; } + } else { + ok = false; } } else if (type.equals("Resource") || isResource(type)) { ok = validateContains(hostContext, errors, ei.getPath(), checkDefn, definition, resource, ei.getElement(), @@ -5917,11 +6042,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat p = this.context.fetchResource(StructureDefinition.class, typeProfile); if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, typeProfile)) { List profileErrors = new ArrayList(); - ok = validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension, pct, mode) && ok; + validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension, pct, mode); // we don't track ok here if (hasErrors(profileErrors)) badProfiles.put(typeProfile, profileErrors); else goodProfiles.put(typeProfile, profileErrors); + } else { + ok = false; } } if (goodProfiles.size() == 1) { @@ -5969,7 +6096,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return ok; + return check(ok); } private boolean isAbstractType(String type) { @@ -6098,11 +6225,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return ok; + return check(ok); } public List assignChildren(ValidatorHostContext hostContext, List errors, StructureDefinition profile, Element resource, - NodeStack stack, SourcedChildDefinitions childDefinitions, List children) throws DefinitionException { + NodeStack stack, SourcedChildDefinitions childDefinitions, List children, BooleanHolder bh) throws DefinitionException { // 2. assign children to a definition // for each definition, for each child, check whether it belongs in the slice ElementDefinition slicer = null; @@ -6140,7 +6267,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (ei.sliceInfo == null) { ei.sliceInfo = new ArrayList<>(); } - unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei); + unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei, bh); } } int last = -1; @@ -6161,12 +6288,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : context.formatMessage(I18nConstants.DEFINED_IN_THE_PROFILE) + " "+profile.getVersionedUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo), errorSummaryForSlicingAsText(ei.sliceInfo)); } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo)); + bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo))); } } else { // Don't raise this if we're in an abstract profile, like Resource if (!childDefinitions.getSource().getAbstract()) { - rule(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.getPath(), (ei.definition != null), I18nConstants.VALIDATION_VAL_PROFILE_NOTALLOWED, profile.getVersionedUrl()); + bh.see(rule(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.getPath(), (ei.definition != null), I18nConstants.VALIDATION_VAL_PROFILE_NOTALLOWED, profile.getVersionedUrl())); } } } @@ -6182,11 +6309,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) { - boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr || ei.getElement().isIgnorePropertyOrder(); - rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getVersionedUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName()); + boolean bok = (ei.definition == null) || (ei.index >= last) || isXmlAttr || ei.getElement().isIgnorePropertyOrder(); + bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), bok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getVersionedUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName())); } if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, I18nConstants.VALIDATION_VAL_PROFILE_SLICEORDER, profile.getVersionedUrl(), ei.getName()); + bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, I18nConstants.VALIDATION_VAL_PROFILE_SLICEORDER, profile.getVersionedUrl(), ei.getName())); } if (ei.definition == null || !isXmlAttr) { last = ei.index; @@ -6218,7 +6345,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat public boolean matchSlice(ValidatorHostContext hostContext, List errors, List sliceInfo, StructureDefinition profile, NodeStack stack, ElementDefinition slicer, boolean unsupportedSlicing, List problematicPaths, int sliceOffset, int i, ElementDefinition ed, - boolean childUnsupportedSlicing, ElementInfo ei) { + boolean childUnsupportedSlicing, ElementInfo ei, BooleanHolder bh) { boolean match = false; if (slicer == null || slicer == ed) { match = nameMatches(ei.getName(), tail(ed.getPath())); @@ -6238,6 +6365,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } catch (FHIRException e) { rule(errors, NO_RULE_DATE, IssueType.PROCESSING, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.SLICING_CANNOT_BE_EVALUATED, e.getMessage()); + bh.fail(); unsupportedSlicing = true; childUnsupportedSlicing = true; } @@ -6268,6 +6396,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ei.sliceindex = i - (sliceOffset + 1); } } + } else { + bh.fail(); } } else if (childUnsupportedSlicing) { problematicPaths.add(ed.getPath()); @@ -6370,7 +6500,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat errors.addAll(invErrors); } } - return ok; + return check(ok); } private boolean isInheritedProfile(List types, String source) { @@ -6406,6 +6536,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } public boolean checkInvariant(ValidatorHostContext hostContext, List errors, String path, StructureDefinition profile, Element resource, Element element, ElementDefinitionConstraintComponent inv) throws FHIRException { + boolean ok = true; if (debug) { System.out.println("inv "+inv.getKey()+" on "+path+" in "+resource.fhirType()+" {{ "+inv.getExpression()+" }}"+time()); } @@ -6416,26 +6547,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String expr = FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey(), context.getVersion()); n = fpe.parse(expr); } catch (FHIRException e) { - rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getVersionedUrl(), path, e.getMessage()); + ok = rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getVersionedUrl(), path, e.getMessage()) && ok; return false; } timeTracker.fpe(t); inv.setUserData("validator.expression.cache", n); } + boolean invOK; String msg; - boolean ok; try { long t = System.nanoTime(); - ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); + invOK = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); timeTracker.fpe(t); msg = fpe.forLog(); } catch (Exception ex) { - ok = false; + invOK = false; msg = ex.getClass().getName()+": "+ex.getMessage(); ex.printStackTrace(); } - if (!ok) { + if (!invOK) { if (wantInvariantInMessage) { msg = msg + " (inv = " + n.toString() + ")"; } @@ -6447,18 +6578,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (inv.hasExtension(ToolingExtensions.EXT_BEST_PRACTICE) && ToolingExtensions.readBooleanExtension(inv, ToolingExtensions.EXT_BEST_PRACTICE)) { if (bpWarnings == BestPracticeWarningLevel.Hint) - hint(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg); - else if (bpWarnings == BestPracticeWarningLevel.Warning) - warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg); + hint(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg); + else if (/*bpWarnings == null || */ bpWarnings == BestPracticeWarningLevel.Warning) + warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg); else if (bpWarnings == BestPracticeWarningLevel.Error) - rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg); + ok = rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg) && ok; } else if (inv.getSeverity() == ConstraintSeverity.ERROR) { - rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg); + ok = rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg) && ok; } else if (inv.getSeverity() == ConstraintSeverity.WARNING) { - warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg); + warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg); } } - return ok; + return check(ok); } private boolean validateObservation(List errors, Element element, NodeStack stack) { @@ -6471,7 +6602,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), performers.size() > 0, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER) && ok; ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_AN_EFFECTIVEDATETIME_OR_AN_EFFECTIVEPERIOD) && ok; - return ok; + return check(ok); } /* @@ -6481,6 +6612,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack, PercentageTracker pct, ValidationMode mode) throws FHIRException { boolean ok = true; + if ("Observation".equals(stack.getLiteralPath())) { + System.out.println("!"); // #FIXME + } // check here if we call validation policy here, and then change it to the new interface assert stack != null; @@ -6531,12 +6665,30 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // validate if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), resourceName.equals(defn.getType()) || resourceName.equals(defn.getTypeTail()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, defn.getType(), resourceName, defn.getVersionedUrl())) { - ok = start(hostContext, errors, element, element, defn, stack, pct, mode); // root is both definition and type + ok = start(hostContext, errors, element, element, defn, stack, pct, mode) && ok; // root is both definition and type } else { ok = false; } } - return ok; + if (ok == hasErrors(errors)) { + throw new Error("ok is wrong. ok = "+ok+", errors = "+errorIds(stack.getLiteralPath(), ok, errors)); + } + return check(ok); + } + + private String errorIds(String path, boolean ok, List errors) { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (ValidationMessage vm : errors) { +// if (vm.isError()) { + b.append(vm.getMessageId()); +// } + } + System.out.println("OK = "+ok+" for "+path); + System.out.println("Errs? = "+hasErrors(errors)); + System.out.println("Errs = "+errors.toString()); + String s = b.toString(); + System.out.println("Ids = "+s); + return s; } private boolean typeMatchesDefn(String name, StructureDefinition defn) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java index b804e6558..d5cbfa23b 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java @@ -85,7 +85,7 @@ public class BundleValidator extends BaseValidator { } } if (type.equals(SEARCHSET)) { - checkSearchSet(errors, bundle, entries, stack); + ok = checkSearchSet(errors, bundle, entries, stack) && ok; } // We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles // validateResourceIds(errors, UNKNOWN_DATE_TIME, entries, stack); @@ -288,7 +288,9 @@ public class BundleValidator extends BaseValidator { return ok; } - private void checkSearchSet(List errors, Element bundle, List entries, NodeStack stack) { + private boolean checkSearchSet(List errors, Element bundle, List entries, NodeStack stack) { + boolean ok = true; + // warning: should have self link List links = new ArrayList(); bundle.getNamedChildren(LINK, links); @@ -315,13 +317,13 @@ public class BundleValidator extends BaseValidator { if (rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), estack.getLiteralPath(), res != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE)) { NodeStack rstack = estack.push(res, -1, null, null); String rt = res.fhirType(); - Boolean ok = checkSearchType(types, rt); - if (ok == null) { + Boolean bok = checkSearchType(types, rt); + if (bok == null) { typeProblem = true; hint(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), selfLink == null, I18nConstants.BUNDLE_SEARCH_ENTRY_TYPE_NOT_SURE); String id = res.getNamedChildValue("id"); warning(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null || "OperationOutcome".equals(rt), I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID); - } else if (ok) { + } else if (bok) { if (!"OperationOutcome".equals(rt)) { String id = res.getNamedChildValue("id"); warning(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID); @@ -335,8 +337,10 @@ public class BundleValidator extends BaseValidator { typeProblem = true; warning(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), estack.getLiteralPath(), false, I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_NO_MODE, rt, types); } + } else { + ok = false; } - } + } if (typeProblem) { warning(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), stack.getLiteralPath(), !typeProblem, I18nConstants.BUNDLE_SEARCH_NO_MODE); } else { @@ -360,17 +364,20 @@ public class BundleValidator extends BaseValidator { String id = res.getNamedChildValue("id"); if (sm != null) { if ("match".equals(sm)) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID); - rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), types.size() == 0 || checkSearchType(types, rt), I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_MODE, rt, types); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID) && ok; + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), types.size() == 0 || checkSearchType(types, rt), I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_MODE, rt, types) && ok; } else if ("include".equals(sm)) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), id != null, I18nConstants.BUNDLE_SEARCH_ENTRY_NO_RESOURCE_ID) && ok; } else { // outcome - rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), "OperationOutcome".equals(rt), I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_OUTCOME, rt); + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, bundle.line(), bundle.col(), rstack.getLiteralPath(), "OperationOutcome".equals(rt), I18nConstants.BUNDLE_SEARCH_ENTRY_WRONG_RESOURCE_TYPE_OUTCOME, rt) && ok; } } + } else { + ok = false; } } } + return ok; } private Boolean checkSearchType(List types, String rt) { @@ -748,27 +755,32 @@ public class BundleValidator extends BaseValidator { } } - private void followResourceLinks(Element entry, Map visitedResources, Map candidateEntries, List candidateResources, List errors, NodeStack stack) { - followResourceLinks(entry, visitedResources, candidateEntries, candidateResources, errors, stack, 0); - } - - private void followResourceLinks(Element entry, Map visitedResources, Map candidateEntries, List candidateResources, List errors, NodeStack stack, int depth) { - Element resource = entry.getNamedChild(RESOURCE); - if (visitedResources.containsValue(resource)) - return; - - visitedResources.put(entry.getNamedChildValue(FULL_URL), resource); - - String type = null; - Set references = findReferences(resource); - for (String reference : references) { - // We don't want errors when just retrieving the element as they will be caught (with better path info) in subsequent processing - IndexedElement r = getFromBundle(stack.getElement(), reference, entry.getChildValue(FULL_URL), new ArrayList(), stack.addToLiteralPath("entry[" + candidateResources.indexOf(resource) + "]"), type, "transaction".equals(stack.getElement().getChildValue(TYPE))); - if (r != null && !visitedResources.containsValue(r.getMatch())) { - followResourceLinks(candidateEntries.get(r.getMatch()), visitedResources, candidateEntries, candidateResources, errors, stack, depth + 1); - } - } - } + // not used? +// private boolean followResourceLinks(Element entry, Map visitedResources, Map candidateEntries, List candidateResources, List errors, NodeStack stack) { +// return followResourceLinks(entry, visitedResources, candidateEntries, candidateResources, errors, stack, 0); +// } +// +// private boolean followResourceLinks(Element entry, Map visitedResources, Map candidateEntries, List candidateResources, List errors, NodeStack stack, int depth) { +// boolean ok = true; +// Element resource = entry.getNamedChild(RESOURCE); +// if (visitedResources.containsValue(resource)) +// return ok; +// +// visitedResources.put(entry.getNamedChildValue(FULL_URL), resource); +// +// String type = null; +// Set references = findReferences(resource); +// for (String reference : references) { +// // We don't want errors when just retrieving the element as they will be caught (with better path info) in subsequent processing +// BooleanHolder bh = new BooleanHolder(); +// IndexedElement r = getFromBundle(stack.getElement(), reference, entry.getChildValue(FULL_URL), new ArrayList(), stack.addToLiteralPath("entry[" + candidateResources.indexOf(resource) + "]"), type, "transaction".equals(stack.getElement().getChildValue(TYPE)), bh); +// ok = ok && bh.ok(); +// if (r != null && !visitedResources.containsValue(r.getMatch())) { +// followResourceLinks(candidateEntries.get(r.getMatch()), visitedResources, candidateEntries, candidateResources, errors, stack, depth + 1); +// } +// } +// return ok; +// } private Set findReferences(Element start) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java index 56bbaca74..f25047ef2 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ConceptMapValidator.java @@ -284,10 +284,12 @@ public class ConceptMapValidator extends BaseValidator { } if (ctxt.hasSourceVS() && ctxt.source != null) { ValidationResult vr = context.validateCode(options.withCheckValueSetOnly().withNoServer(), ctxt.source.url, ctxt.source.version, c, null, ctxt.sourceScope.vs); - warningOrError(ctxt.source.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-09-06", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), vr.isOk(), I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID_VS, c, ctxt.sourceScope.vs.getVersionedUrl()); + if (!warningOrError(ctxt.source.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-09-06", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), vr.isOk(), I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID_VS, c, ctxt.sourceScope.vs.getVersionedUrl())) { + ok = (ctxt.source.cs.getContent() != CodeSystemContentMode.COMPLETE) & ok; + } } } else { - ok = false; + ok = (ctxt.source.cs.getContent() != CodeSystemContentMode.COMPLETE) & ok; } } else { addToBatch(code, cstack, ctxt.source, ctxt.sourceScope); @@ -319,10 +321,12 @@ public class ConceptMapValidator extends BaseValidator { } if (ctxt.hasTargetVS() && ctxt.target != null) { ValidationResult vr = context.validateCode(options.withCheckValueSetOnly().withNoServer(), ctxt.target.url, ctxt.target.version, c, null, ctxt.targetScope.vs); - warningOrError(ctxt.target.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-09-06", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), vr.isOk(), I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID_VS, c, ctxt.targetScope.vs.getVersionedUrl()); + if (!warningOrError(ctxt.target.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-09-06", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), vr.isOk(), I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID_VS, c, ctxt.targetScope.vs.getVersionedUrl())) { + ok = (ctxt.target.cs.getContent() != CodeSystemContentMode.COMPLETE) && ok; + } } } else { - ok = false; + ok = (ctxt.source.cs.getContent() != CodeSystemContentMode.COMPLETE) & ok; } } else { addToBatch(code, cstack, ctxt.target, ctxt.targetScope); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java index 82baf3be9..5499a3005 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java @@ -482,7 +482,7 @@ public class QuestionnaireValidator extends BaseValidator { } private boolean validateQuestionnaireResponseItem(ValidatorHostContext hostContext, QuestionnaireWithContext qsrc, QuestionnaireItemComponent qItem, List errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QStack qstack) { - BooleanValue ok = new BooleanValue(true); + BooleanHolder ok = new BooleanHolder(); String text = element.getNamedChildValue("text"); ok.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), I18nConstants.QUESTIONNAIRE_QR_ITEM_TEXT, qItem.getLinkId())); @@ -584,7 +584,7 @@ public class QuestionnaireValidator extends BaseValidator { } if (qItem.getType() != QuestionnaireItemType.GROUP) { // if it's a group, we already have an error before getting here, so no need to hammer away on that - validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot, qstack); + ok.see(validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot, qstack)); } i++; } @@ -601,7 +601,7 @@ public class QuestionnaireValidator extends BaseValidator { } else { ok.see(validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, element, stack, inProgress, questionnaireResponseRoot, qstack)); } - return ok.isValue(); + return ok.ok(); } private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, List answers) { @@ -711,7 +711,7 @@ public class QuestionnaireValidator extends BaseValidator { } - private String validateQuestionnaireResponseItemType(List errors, Element element, NodeStack stack, BooleanValue ok, String... types) { + private String validateQuestionnaireResponseItemType(List errors, Element element, NodeStack stack, BooleanHolder ok, String... types) { List values = new ArrayList(); element.getNamedChildrenWithWildcard("value[x]", values); for (int i = 0; i < types.length; i++) { 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 fa9c1ceb1..0739a7b35 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 @@ -74,7 +74,7 @@ public class StructureDefinitionValidator extends BaseValidator { String url = src.getNamedChildValue("url"); sd = loadAsSD(src); - checkExtensionContext(errors, src, stack); + ok = checkExtensionContext(errors, src, stack) && ok; List snapshot = sd.getSnapshot().getElement(); sd.setSnapshot(null); @@ -83,9 +83,9 @@ public class StructureDefinitionValidator extends BaseValidator { if (warning(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) { if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasDerivation(), I18nConstants.SD_MUST_HAVE_DERIVATION, sd.getUrl())) { boolean bok = base.getAbstract() || sd.hasKind() && sd.getKind() == base.getKind(); - rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_CONSTRAINED_KIND_NO_MATCH, sd.getKind().toCode(), base.getKind().toCode(), base.getType(), base.getUrl()); + ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_CONSTRAINED_KIND_NO_MATCH, sd.getKind().toCode(), base.getKind().toCode(), base.getType(), base.getUrl()) && ok; if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { - rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && sd.getType().equals(base.getType()), I18nConstants.SD_CONSTRAINED_TYPE_NO_MATCH, sd.getType(), base.getType()); + ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && sd.getType().equals(base.getType()), I18nConstants.SD_CONSTRAINED_TYPE_NO_MATCH, sd.getType(), base.getType()) && ok; List msgs = new ArrayList<>(); ProfileUtilities pu = new ProfileUtilities(context, msgs, null); pu.setForPublication(forPublication); @@ -102,7 +102,7 @@ public class StructureDefinitionValidator extends BaseValidator { msg.setLocation(stack.getLiteralPath()); } errors.add(msg); - ok = false; + ok = (!msg.isError()) && ok; } } if (!snapshot.isEmpty() && wantCheckSnapshotUnchanged) { @@ -111,7 +111,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), was == is, I18nConstants.SNAPSHOT_EXISTING_PROBLEM, was, is) && ok; } } else { - rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && !sd.getType().equals(base.getType()), I18nConstants.SD_SPECIALIZED_TYPE_MATCHES, sd.getType(), base.getType()); + ok = rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasType() && !sd.getType().equals(base.getType()), I18nConstants.SD_SPECIALIZED_TYPE_MATCHES, sd.getType(), base.getType()) && ok; } } else { ok = false; @@ -144,6 +144,8 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateObligationProfile(errors, differential, stack.push(differential, -1, null, null), base) && ok; } } + } else { + ok = false; } } } @@ -173,7 +175,7 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); ok = false; } - return ok; + return check(ok); } @@ -218,7 +220,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateObligationProfileElement(errors, element, stack.push(element, cc, null, null), base) && ok; cc++; } - return ok; + return check(ok); } private boolean validateObligationProfileElement(List errors, Element element, NodeStack push, StructureDefinition base) { @@ -267,7 +269,7 @@ public class StructureDefinitionValidator extends BaseValidator { I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL, id, child.getName()); } } - return ok; + return check(ok); } else { return false; } @@ -317,10 +319,11 @@ public class StructureDefinitionValidator extends BaseValidator { I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL_ON_BINDING, id, child.getName()); } } - return ok; + return check(ok); } - private void checkExtensionContext(List errors, Element src, NodeStack stack) { + private boolean checkExtensionContext(List errors, Element src, NodeStack stack) { + boolean ok = true; String type = src.getNamedChildValue("type"); List eclist = src.getChildren("context"); List cilist = src.getChildren("contextInvariant"); @@ -341,7 +344,7 @@ public class StructureDefinitionValidator extends BaseValidator { warning(errors, "2023-04-23", IssueType.BUSINESSRULE, n.getLiteralPath(), false, I18nConstants.SD_CONTEXT_SHOULD_NOT_BE_ELEMENT, cv); } } else { - rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_WHEN_NOT_EXTENSION, type); + ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_WHEN_NOT_EXTENSION, type) && ok; } } i = 0; @@ -350,9 +353,10 @@ public class StructureDefinitionValidator extends BaseValidator { if ("Extension".equals(type)) { } else { - rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION, type); + ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION, type) && ok; } } + return check(ok); } 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) { @@ -364,18 +368,18 @@ public class StructureDefinitionValidator extends BaseValidator { 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; + return check(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, StructureDefinition base) { boolean ok = true; boolean typeMustSupport = false; String path = element.getNamedChildValue("path"); - rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), typeName == null || path == null || path.equals(typeName) || path.startsWith(typeName+"."), I18nConstants.SD_PATH_TYPE_MISMATCH, typeName, path); + ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), typeName == null || path == null || path.equals(typeName) || path.startsWith(typeName+"."), I18nConstants.SD_PATH_TYPE_MISMATCH, typeName, path) && ok; if (!snapshot) { - rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path); + ok = rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path) && ok; } - rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), snapshot || !constraint || !element.hasChild("meaningWhenMissing") || meaningWhenMissingAllowed(element), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path); + ok = rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), snapshot || !constraint || !element.hasChild("meaningWhenMissing") || meaningWhenMissingAllowed(element), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path) && ok; List types = element.getChildrenByName("type"); Set typeCodes = new HashSet<>(); @@ -398,7 +402,7 @@ public class StructureDefinitionValidator extends BaseValidator { } if (Utilities.noString(tc) && type.hasChild("code")) { if (VersionUtilities.isR4Plus(context.getVersion())) { - rule(errors, "2023-03-16", IssueType.INVALID, stack.getLiteralPath(), false, I18nConstants.SD_NO_TYPE_CODE_ON_CODE, path, sd.getId()); + ok = rule(errors, "2023-03-16", IssueType.INVALID, stack.getLiteralPath(), false, I18nConstants.SD_NO_TYPE_CODE_ON_CODE, path, sd.getId()) && ok; } } if (!Utilities.noString(tc)) { @@ -489,6 +493,8 @@ public class StructureDefinitionValidator extends BaseValidator { warning(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), !repeating, I18nConstants.SD_VALUE_TYPE_REPEAT_WARNING_DOTNET, element.getIdBase(), "pattern"); } } + } else { + ok = false; } // if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint } @@ -498,7 +504,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl, snapshot, base) && ok; cc++; } - return ok; + return check(ok); } private boolean validateElementDefinitionInvariant(List errors, Element invariant, NodeStack stack, Map invariantMap, List elements, Element element, @@ -559,7 +565,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return ok; + return check(ok); } private boolean haseHasInvariant(StructureDefinition base, String key) { @@ -792,7 +798,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return ok; + return check(ok); } private Set getListofBindableTypes(Set types) { @@ -836,7 +842,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return ok; + return check(ok); } private boolean validateProfileTypeOrTarget(List errors, Element profile, String code, NodeStack stack, String path) { @@ -874,7 +880,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return ok; + return check(ok); } private String getTypeCodeFromSD(StructureDefinition sd, String path) { @@ -931,7 +937,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return ok; + return check(ok); } private boolean checkIsModifierExtension(StructureDefinition t) { @@ -965,7 +971,7 @@ public class StructureDefinitionValidator extends BaseValidator { } else { ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code) && ok; } - return ok; + return check(ok); } private boolean isReferenceableTarget(StructureDefinition t) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java index 97559a04e..4037fbcd2 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java @@ -175,9 +175,9 @@ public class ValueSetValidator extends BaseValidator { } for (VSCodingValidationRequest cv : batch) { if (version == null) { - ok = warningOrHint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, cv.getCoding().getCode()) && ok; + warningOrHint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, cv.getCoding().getCode()); } else { - ok = warningOrHint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, cv.getCoding().getCode()) && ok; + warningOrHint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, cv.getCoding().getCode()); } } } catch (Exception e) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java index 5c33e7cb4..f8e2b699a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java @@ -148,14 +148,16 @@ public class ProfileValidator extends BaseValidator { return key.startsWith("txt-"); } - private void checkExtensions(StructureDefinition profile, List errors, String kind, ElementDefinition ec) { + private boolean checkExtensions(StructureDefinition profile, List errors, String kind, ElementDefinition ec) { if (!ec.getType().isEmpty() && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile()) { String url = ec.getType().get(0).getProfile().get(0).getValue(); StructureDefinition defn = context.fetchResource(StructureDefinition.class, url); if (defn == null) { defn = getXverExt(profile, errors, url); } - rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), defn != null, "Unable to find Extension '"+url+"' referenced at "+profile.getUrl()+" "+kind+" "+ec.getPath()+" ("+ec.getSliceName()+")"); + return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), defn != null, "Unable to find Extension '"+url+"' referenced at "+profile.getUrl()+" "+kind+" "+ec.getPath()+" ("+ec.getSliceName()+")"); + } else { + return true; } } diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/iso3166.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/iso3166.cache index d63bc7ca1..be13771ad 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/iso3166.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/iso3166.cache @@ -14,7 +14,6 @@ v: { "code" : "NL", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -37,7 +36,6 @@ v: { "code" : "NL", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -60,7 +58,6 @@ v: { "code" : "NL", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -82,7 +79,6 @@ v: { "code" : "NL", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -104,7 +100,6 @@ v: { "code" : "NL", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -127,7 +122,6 @@ v: { "code" : "NL", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -149,7 +143,6 @@ v: { "code" : "US", "system" : "urn:iso:std:iso:3166", "version" : "2018", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -166,6 +159,28 @@ v: { "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }] }}#### +v: { + "display" : "United States of America", + "code" : "US", + "system" : "urn:iso:std:iso:3166", + "version" : "2018", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:iso:std:iso:3166", + "code" : "US", + "display" : "United States of America" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### v: { "display" : "United States of America", "code" : "US", diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/loinc.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/loinc.cache index 4a4ad1221..5dd1a6457 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/loinc.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/3.0.2/loinc.cache @@ -58,7 +58,6 @@ v: { "code" : "19935-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -80,7 +79,6 @@ v: { "code" : "19935-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -102,7 +100,6 @@ v: { "code" : "19935-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -123,7 +120,6 @@ v: { "code" : "19935-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -145,7 +141,6 @@ v: { "code" : "28655-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -167,7 +162,6 @@ v: { "code" : "28655-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -189,7 +183,6 @@ v: { "code" : "28655-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -212,7 +205,6 @@ v: { "code" : "29299-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -235,7 +227,6 @@ v: { "code" : "10183-2", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -258,7 +249,6 @@ v: { "code" : "48765-2", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -280,7 +270,6 @@ v: { "code" : "46241-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -302,7 +291,6 @@ v: { "code" : "18842-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -325,7 +313,6 @@ v: { "code" : "18842-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -347,7 +334,6 @@ v: { "code" : "18842-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -369,7 +355,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Allergies' for http://loinc.org#48765-2 - should be one of 28 choices: 'Allergies and adverse reactions Document', 'Allergies &or adverse reactions Doc', '临床文档型' (zh-CN), '临床文档' (zh-CN), '文档' (zh-CN), '文书' (zh-CN), '医疗文书' (zh-CN), '临床医疗文书 医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 变态反应与不良反应 文档.其他' (zh-CN), '杂项类文档' (zh-CN), '其他文档 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 杂项' (zh-CN), '杂项类' (zh-CN), '杂项试验 过敏反应' (zh-CN), '过敏' (zh-CN), 'Allergie e reazioni avverse Documentazione miscellanea Miscellanea Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Документ Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -392,7 +377,6 @@ v: { "code" : "8648-8", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -415,7 +399,6 @@ v: { "code" : "78375-3", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -438,7 +421,6 @@ v: { "code" : "75311-1", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -461,7 +443,6 @@ v: { "code" : "42347-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -484,7 +465,6 @@ v: { "code" : "42346-7", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -507,7 +487,6 @@ v: { "code" : "42344-2", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -530,7 +509,6 @@ v: { "code" : "10164-2", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -552,7 +530,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Plan of care' for http://loinc.org#18776-5 - should be one of 30 choices: 'Plan of care note', 'Plan of care note', '临床文档型' (zh-CN), '临床文档' (zh-CN), '文档' (zh-CN), '文书' (zh-CN), '医疗文书' (zh-CN), '临床医疗文书 事件发生的地方' (zh-CN), '场景' (zh-CN), '环境' (zh-CN), '背景 医疗服务(照护服务、护理服务、护理、照护、医疗照护、诊疗、诊疗服务、照顾、看护)计划(方案)记录 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 文档本体' (zh-CN), '临床文档本体' (zh-CN), '文档本体' (zh-CN), '文书本体' (zh-CN), '医疗文书本体' (zh-CN), '临床医疗文书本体 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 未加明确说明的角色 笔记' (zh-CN), '按语' (zh-CN), '注释' (zh-CN), '说明' (zh-CN), '票据' (zh-CN), '单据' (zh-CN), '证明书' (zh-CN) or 'Documentazione dell'ontologia Osservazione Piano di cura Punto nel tempo (episodio) Ruolo non specificato' (it-IT) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -575,7 +552,6 @@ v: { "code" : "47420-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -598,7 +574,6 @@ v: { "code" : "47519-4", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -620,7 +595,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Review of systems Narrative Reporte' for http://loinc.org#10187-3 - should be one of 41 choices: 'Review of systems Narrative - Reported', 'Review of systems', '医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 历史纪录与体格检查 历史纪录与体格检查.历史记录' (zh-CN), '历史纪录与体格检查.历史记录类' (zh-CN), '历史纪录与体格检查.历史记录类别' (zh-CN), '历史纪录与体格检查.病史' (zh-CN), '历史纪录与体格检查.病史类' (zh-CN), '历史纪录与体格检查.病史类别' (zh-CN), '历史纪录与体格检查.病史记录' (zh-CN), '历史纪录与体格检查.病史记录类' (zh-CN), '历史纪录与体格检查.病史记录类别' (zh-CN), '历史纪录与体格检查小节.历史记录' (zh-CN), '历史纪录与体格检查小节.历史记录类' (zh-CN), '历史纪录与体格检查小节.历史记录类别' (zh-CN), '历史纪录与体格检查小节.病史' (zh-CN), '历史纪录与体格检查小节.病史类' (zh-CN), '历史纪录与体格检查小节.病史类别 历史纪录与体格检查小节 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 叙述' (zh-CN), '叙述性文字' (zh-CN), '报告' (zh-CN), '报告型' (zh-CN), '文字叙述' (zh-CN), '文本叙述型' (zh-CN), '文本描述' (zh-CN), '文本描述型 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 病史与体格检查 系统回顾' (zh-CN), '系统审核' (zh-CN), 'Anamnesi Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Анамнестические сведения' (ru-RU), 'Сообщенная третьим лицом информация Описательный Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -642,7 +616,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Administrative information' for http://loinc.org#87504-7 - should be 'LCDS v4.00 - Administrative information - discharge [CMS Assessment]' (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -665,6 +638,28 @@ v: { "code" : "2069-3", "system" : "http://loinc.org", "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "18842-5", + "display" : "Discharge Summary" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Discharge summary", + "code" : "18842-5", + "system" : "http://loinc.org", + "version" : "2.74", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/iso3166.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/iso3166.cache index d73b5b0b1..338c0c8f6 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/iso3166.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/iso3166.cache @@ -141,6 +141,28 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Alderney' for urn:iso:std:iso:3166#NO - should be 'Norway' (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:iso:std:iso:3166", + "code" : "US", + "display" : "United States of America" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "United States of America", + "code" : "US", + "system" : "urn:iso:std:iso:3166", + "version" : "2018", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/lang.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/lang.cache index 06ae7c265..a7d233555 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/lang.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/lang.cache @@ -13,7 +13,6 @@ v: { "display" : "German (Switzerland)", "code" : "de-CH", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -35,7 +34,6 @@ v: { "display" : "German (Region=Switzerland)", "code" : "de-CH", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -56,7 +54,6 @@ v: { "display" : "German (Region=Switzerland)", "code" : "de-CH", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -77,7 +74,6 @@ v: { "display" : "French", "code" : "fr", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -99,7 +95,6 @@ v: { "display" : "French", "code" : "fr", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -120,7 +115,6 @@ v: { "display" : "French", "code" : "fr", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -141,7 +135,6 @@ v: { "display" : "English", "code" : "en", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -163,7 +156,6 @@ v: { "display" : "English", "code" : "en", "system" : "urn:ietf:bcp:47", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -180,6 +172,71 @@ v: { "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }] }}#### +v: { + "display" : "English", + "code" : "en", + "system" : "urn:ietf:bcp:47", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:ietf:bcp:47", + "code" : "de-CH", + "display" : "German (Region=Switzerland)" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "German (Region=Switzerland)", + "code" : "de-CH", + "system" : "urn:ietf:bcp:47", + "unknown-systems" : "", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:ietf:bcp:47", + "code" : "fr", + "display" : "French" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "French", + "code" : "fr", + "system" : "urn:ietf:bcp:47", + "unknown-systems" : "", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:ietf:bcp:47", + "code" : "en", + "display" : "English" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### v: { "display" : "English", "code" : "en", diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache index 060df9b1d..11d0b0308 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/loinc.cache @@ -3861,7 +3861,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Allergies and Adverse Reactions' for http://loinc.org#48765-2 - should be one of 28 choices: 'Allergies and adverse reactions Document', 'Allergies &or adverse reactions Doc', '临床文档型' (zh-CN), '临床文档' (zh-CN), '文档' (zh-CN), '文书' (zh-CN), '医疗文书' (zh-CN), '临床医疗文书 医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 变态反应与不良反应 文档.其他' (zh-CN), '杂项类文档' (zh-CN), '其他文档 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 杂项' (zh-CN), '杂项类' (zh-CN), '杂项试验 过敏反应' (zh-CN), '过敏' (zh-CN), 'Allergie e reazioni avverse Documentazione miscellanea Miscellanea Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Документ Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -3883,7 +3882,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Medication List' for http://loinc.org#10160-0 - should be one of 48 choices: 'History of Medication use Narrative', 'Hx of Medication use', '医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 历史' (zh-CN), '史' (zh-CN), '病史 历史纪录与体格检查 历史纪录与体格检查.历史记录' (zh-CN), '历史纪录与体格检查.历史记录类' (zh-CN), '历史纪录与体格检查.历史记录类别' (zh-CN), '历史纪录与体格检查.病史' (zh-CN), '历史纪录与体格检查.病史类' (zh-CN), '历史纪录与体格检查.病史类别' (zh-CN), '历史纪录与体格检查.病史记录' (zh-CN), '历史纪录与体格检查.病史记录类' (zh-CN), '历史纪录与体格检查.病史记录类别' (zh-CN), '历史纪录与体格检查小节.历史记录' (zh-CN), '历史纪录与体格检查小节.历史记录类' (zh-CN), '历史纪录与体格检查小节.历史记录类别' (zh-CN), '历史纪录与体格检查小节.病史' (zh-CN), '历史纪录与体格检查小节.病史类' (zh-CN), '历史纪录与体格检查小节.病史类别 历史纪录与体格检查小节 叙述' (zh-CN), '叙述性文字' (zh-CN), '报告' (zh-CN), '报告型' (zh-CN), '文字叙述' (zh-CN), '文本叙述型' (zh-CN), '文本描述' (zh-CN), '文本描述型 处理用药' (zh-CN), '处理用药物' (zh-CN), '处理药物' (zh-CN), '治疗用药' (zh-CN), '治疗用药物' (zh-CN), '用药' (zh-CN), '药物处理' (zh-CN), '药物治疗' (zh-CN), '治疗药物 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 病史与体格检查 药物使用历史' (zh-CN), '药物使用史' (zh-CN), 'Anamnesi paziente Punto nel tempo (episodio) Storia' (it-IT), 'Anamnesi' (it-IT), 'История Лекарственный анамнез Описательный Точка во времени' (ru-RU), 'Момент' (ru-RU) or 'anamnese geneesmiddelen' (nl-NL) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -3905,7 +3903,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Problem List' for http://loinc.org#11450-4 - should be one of 53 choices: 'Problem list - Reported', 'Problem list Reported', '分类型应答' (zh-CN), '分类型结果' (zh-CN), '名义性' (zh-CN), '名称型' (zh-CN), '名词型' (zh-CN), '名词性' (zh-CN), '标称性' (zh-CN), '没有自然次序的名义型或分类型应答 医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 历史纪录与体格检查 历史纪录与体格检查.历史记录' (zh-CN), '历史纪录与体格检查.历史记录类' (zh-CN), '历史纪录与体格检查.历史记录类别' (zh-CN), '历史纪录与体格检查.病史' (zh-CN), '历史纪录与体格检查.病史类' (zh-CN), '历史纪录与体格检查.病史类别' (zh-CN), '历史纪录与体格检查.病史记录' (zh-CN), '历史纪录与体格检查.病史记录类' (zh-CN), '历史纪录与体格检查.病史记录类别' (zh-CN), '历史纪录与体格检查小节.历史记录' (zh-CN), '历史纪录与体格检查小节.历史记录类' (zh-CN), '历史纪录与体格检查小节.历史记录类别' (zh-CN), '历史纪录与体格检查小节.病史' (zh-CN), '历史纪录与体格检查小节.病史类' (zh-CN), '历史纪录与体格检查小节.病史类别 历史纪录与体格检查小节 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 病史与体格检查 问题目录' (zh-CN), '问题清单 难题' (zh-CN), '困难' (zh-CN), '棘手问题' (zh-CN), '麻烦' (zh-CN), '乱子' (zh-CN), '疑难问题' (zh-CN), '疑难' (zh-CN), 'Finding' (pt-BR), 'Findings' (pt-BR), 'Point in time' (pt-BR), 'Random' (pt-BR), 'Nominal' (pt-BR), 'Anamnesi Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Анамнестические сведения' (ru-RU), 'Сообщенная третьим лицом информация Номинальный' (ru-RU), 'Именной Список проблем Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -3927,7 +3924,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'History of Immunizations' for http://loinc.org#11369-6 - should be one of 43 choices: 'History of Immunization Narrative', 'Hx of Immunization', '免疫接种历史' (zh-CN), '免疫史' (zh-CN), '接种史' (zh-CN), '免疫接种史 医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 历史' (zh-CN), '史' (zh-CN), '病史 历史纪录与体格检查 历史纪录与体格检查.历史记录' (zh-CN), '历史纪录与体格检查.历史记录类' (zh-CN), '历史纪录与体格检查.历史记录类别' (zh-CN), '历史纪录与体格检查.病史' (zh-CN), '历史纪录与体格检查.病史类' (zh-CN), '历史纪录与体格检查.病史类别' (zh-CN), '历史纪录与体格检查.病史记录' (zh-CN), '历史纪录与体格检查.病史记录类' (zh-CN), '历史纪录与体格检查.病史记录类别' (zh-CN), '历史纪录与体格检查小节.历史记录' (zh-CN), '历史纪录与体格检查小节.历史记录类' (zh-CN), '历史纪录与体格检查小节.历史记录类别' (zh-CN), '历史纪录与体格检查小节.病史' (zh-CN), '历史纪录与体格检查小节.病史类' (zh-CN), '历史纪录与体格检查小节.病史类别 历史纪录与体格检查小节 叙述' (zh-CN), '叙述性文字' (zh-CN), '报告' (zh-CN), '报告型' (zh-CN), '文字叙述' (zh-CN), '文本叙述型' (zh-CN), '文本描述' (zh-CN), '文本描述型 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 病史与体格检查' (zh-CN), 'Anamnesi paziente Punto nel tempo (episodio) Storia' (it-IT), 'Anamnesi' (it-IT), 'История Описательный Точка во времени' (ru-RU), 'Момент' (ru-RU), 'anamnese vaccinatie' (nl-NL) or 'Immunisierungsstatus' (de-AT) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -3949,7 +3945,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Social History' for http://loinc.org#29762-2 - should be one of 45 choices: 'Social history Narrative', 'Social Hx', '医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 历史纪录与体格检查 历史纪录与体格检查.历史记录' (zh-CN), '历史纪录与体格检查.历史记录类' (zh-CN), '历史纪录与体格检查.历史记录类别' (zh-CN), '历史纪录与体格检查.病史' (zh-CN), '历史纪录与体格检查.病史类' (zh-CN), '历史纪录与体格检查.病史类别' (zh-CN), '历史纪录与体格检查.病史记录' (zh-CN), '历史纪录与体格检查.病史记录类' (zh-CN), '历史纪录与体格检查.病史记录类别' (zh-CN), '历史纪录与体格检查小节.历史记录' (zh-CN), '历史纪录与体格检查小节.历史记录类' (zh-CN), '历史纪录与体格检查小节.历史记录类别' (zh-CN), '历史纪录与体格检查小节.病史' (zh-CN), '历史纪录与体格检查小节.病史类' (zh-CN), '历史纪录与体格检查小节.病史类别 历史纪录与体格检查小节 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 叙述' (zh-CN), '叙述性文字' (zh-CN), '报告' (zh-CN), '报告型' (zh-CN), '文字叙述' (zh-CN), '文本叙述型' (zh-CN), '文本描述' (zh-CN), '文本描述型 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 病史与体格检查 社会历史' (zh-CN), 'Finding' (pt-BR), 'Findings' (pt-BR), 'Point in time' (pt-BR), 'Random' (pt-BR), 'Narrative' (pt-BR), 'Report' (pt-BR), 'Anamnesi Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Описательный Социальный анамнез Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -3972,7 +3967,6 @@ v: { "code" : "72166-2", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -3995,6 +3989,117 @@ v: { "code" : "LA15920-4", "system" : "http://loinc.org", "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "60591-5", + "display" : "Patient summary Document" +}, "valueSet" :null, "langs":"de-CH", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "code" : "60591-5", + "system" : "http://loinc.org", + "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "60591-5", + "display" : "Patient Summary Document" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Patient summary Document", + "code" : "60591-5", + "system" : "http://loinc.org", + "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "version" : "current", + "code" : "56445-0", + "display" : "Medication summary Doc" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Medication summary Document", + "code" : "56445-0", + "system" : "http://loinc.org", + "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "34133-9", + "display" : "Summary of episode note" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Summary of episode note", + "code" : "34133-9", + "system" : "http://loinc.org", + "version" : "2.74", + "unknown-systems" : "", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "18842-5", + "display" : "Discharge Summary" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Discharge summary", + "code" : "18842-5", + "system" : "http://loinc.org", + "version" : "2.74", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache index 5e7ace6ee..a58ca997c 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache @@ -2878,6 +2878,71 @@ v: { "severity" : "error", "error" : "The provided code 'http://snomed.info/sct|http://snomed.info/sct/2011000195101#46224007' is not in the value set 'http://fhir.ch/ig/ch-ig/ValueSet/OrganizationType|0.1.0' (from Tx-Server)", "class" : "UNKNOWN", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "255604002", + "display" : "Mild" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Mild", + "code" : "255604002", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230731", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "77176002" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Smoker", + "code" : "77176002", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230731", + "unknown-systems" : "", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "38341003" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "High blood pressure", + "code" : "38341003", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20230731", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/http___unstats.un.org_unsd_methods_m49_m49.htm.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/http___unstats.un.org_unsd_methods_m49_m49.htm.cache index 2a3fff705..e21bd389b 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/http___unstats.un.org_unsd_methods_m49_m49.htm.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/http___unstats.un.org_unsd_methods_m49_m49.htm.cache @@ -13,7 +13,6 @@ v: { "display" : "World", "code" : "001", "system" : "http://unstats.un.org/unsd/methods/m49/m49.htm", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -30,6 +29,27 @@ v: { "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" }] }}#### +v: { + "display" : "World", + "code" : "001", + "system" : "http://unstats.un.org/unsd/methods/m49/m49.htm", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://unstats.un.org/unsd/methods/m49/m49.htm", + "code" : "001", + "display" : "World" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### v: { "display" : "World", "code" : "001", diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/loinc.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/loinc.cache index db6c0ff22..1fe216ac1 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/loinc.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/loinc.cache @@ -167,7 +167,6 @@ v: { "code" : "85354-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -190,7 +189,6 @@ v: { "code" : "8480-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -213,7 +211,6 @@ v: { "code" : "8462-4", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -235,7 +232,6 @@ v: { "code" : "85354-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -258,7 +254,6 @@ v: { "code" : "85354-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -280,7 +275,6 @@ v: { "code" : "85354-9", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -302,7 +296,6 @@ v: { "code" : "8480-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -325,7 +318,6 @@ v: { "code" : "8480-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -347,7 +339,6 @@ v: { "code" : "8480-6", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -369,7 +360,6 @@ v: { "code" : "8462-4", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -392,7 +382,6 @@ v: { "code" : "8462-4", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -414,7 +403,6 @@ v: { "code" : "8462-4", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -436,7 +424,6 @@ v: { "code" : "56445-0", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -460,7 +447,6 @@ v: { "code" : "56445-0", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -483,7 +469,6 @@ v: { "code" : "56445-0", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -506,7 +491,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Allergies and adverse reactions' for http://loinc.org#48765-2 - should be one of 28 choices: 'Allergies and adverse reactions Document', 'Allergies &or adverse reactions Doc', '临床文档型' (zh-CN), '临床文档' (zh-CN), '文档' (zh-CN), '文书' (zh-CN), '医疗文书' (zh-CN), '临床医疗文书 医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 变态反应与不良反应 文档.其他' (zh-CN), '杂项类文档' (zh-CN), '其他文档 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 杂项' (zh-CN), '杂项类' (zh-CN), '杂项试验 过敏反应' (zh-CN), '过敏' (zh-CN), 'Allergie e reazioni avverse Documentazione miscellanea Miscellanea Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Документ Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -528,7 +512,6 @@ v: { "code" : "56445-0", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -552,7 +535,6 @@ v: { "code" : "56445-0", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -575,7 +557,6 @@ v: { "code" : "56445-0", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -598,7 +579,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Allergies and adverse reactions' for http://loinc.org#48765-2 - should be one of 28 choices: 'Allergies and adverse reactions Document', 'Allergies &or adverse reactions Doc', '临床文档型' (zh-CN), '临床文档' (zh-CN), '文档' (zh-CN), '文书' (zh-CN), '医疗文书' (zh-CN), '临床医疗文书 医疗服务对象' (zh-CN), '客户' (zh-CN), '病人' (zh-CN), '病患' (zh-CN), '病号' (zh-CN), '超系统 - 病人 发现是一个原子型临床观察指标,并不是作为印象的概括陈述。体格检查、病史、系统检查及其他此类观察指标的属性均为发现。它们的标尺对于编码型发现可能是名义型,而对于叙述型文本之中所报告的发现,则可能是叙述型。' (zh-CN), '发现物' (zh-CN), '所见' (zh-CN), '结果' (zh-CN), '结论 变态反应与不良反应 文档.其他' (zh-CN), '杂项类文档' (zh-CN), '其他文档 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 杂项' (zh-CN), '杂项类' (zh-CN), '杂项试验 过敏反应' (zh-CN), '过敏' (zh-CN), 'Allergie e reazioni avverse Documentazione miscellanea Miscellanea Osservazione paziente Punto nel tempo (episodio)' (it-IT), 'Документ Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -620,7 +600,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'ingeademde O2' for http://loinc.org#3151-8 - should be one of 2 choices: 'Inhaled oxygen flow rate' or 'Inhaled O2 flow rate' (for the language(s) 'en') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -643,7 +622,6 @@ v: { "code" : "3151-8", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -665,7 +643,6 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Cholesterol [Moles/​volume] in Serum or Plasma' for http://loinc.org#35200-5 - should be one of 50 choices: 'Cholesterol [Mass or Moles/volume] in Serum or Plasma', 'Cholest SerPl-msCnc', '化学' (zh-CN), '化学检验项目' (zh-CN), '化学检验项目类' (zh-CN), '化学类' (zh-CN), '化学试验' (zh-CN), '非刺激耐受型化学检验项目' (zh-CN), '非刺激耐受型化学检验项目类' (zh-CN), '非刺激耐受型化学试验' (zh-CN), '非刺激耐受型化学试验类 可用数量表示的' (zh-CN), '定量性' (zh-CN), '数值型' (zh-CN), '数量型' (zh-CN), '连续数值型标尺 总胆固醇' (zh-CN), '胆固醇总计' (zh-CN), '胆甾醇' (zh-CN), '脂类' (zh-CN), '脂质 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 血清或血浆 质量或摩尔浓度' (zh-CN), '质量或摩尔浓度(单位体积)' (zh-CN), '质量或物质的量浓度(单位体积)' (zh-CN), 'Juhuslik Kvantitatiivne Plasma Seerum Seerum või plasma' (et-EE), 'Cholest' (pt-BR), 'Chol' (pt-BR), 'Choles' (pt-BR), 'Lipid' (pt-BR), 'Cholesterol total' (pt-BR), 'Cholesterols' (pt-BR), 'Level' (pt-BR), 'Point in time' (pt-BR), 'Random' (pt-BR), 'SerPl' (pt-BR), 'SerPlas' (pt-BR), 'SerP' (pt-BR), 'Serum' (pt-BR), 'SR' (pt-BR), 'Plasma' (pt-BR), 'Pl' (pt-BR), 'Plsm' (pt-BR), 'Quantitative' (pt-BR), 'QNT' (pt-BR), 'Quant' (pt-BR), 'Quan' (pt-BR), 'Chemistry' (pt-BR), 'Chimica Concentrazione Sostanza o Massa Plasma Punto nel tempo (episodio) Siero Siero o Plasma' (it-IT), 'Количественный Массовая или Молярная Концентрация Плазма Сыворотка Сыворотка или Плазма Точка во времени' (ru-RU) or 'Момент Холестерин' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -688,7 +665,6 @@ v: { "code" : "13457-7", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -711,7 +687,6 @@ v: { "code" : "29463-7", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -734,7 +709,6 @@ v: { "code" : "29463-7", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -757,7 +731,6 @@ v: { "code" : "35200-5", "system" : "http://loinc.org", "version" : "2.74", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -779,6 +752,73 @@ v: { "severity" : "error", "error" : "Wrong Display Name 'Triglyceride [Moles/​volume] in Serum or Plasma' for http://loinc.org#35217-9 - should be one of 50 choices: 'Triglyceride [Mass or Moles/volume] in Serum or Plasma', 'Trigl SerPl-msCnc', 'TG' (zh-CN), 'Trigly' (zh-CN), '甘油三脂' (zh-CN), '甘油三酸酯' (zh-CN), '三酸甘油酯' (zh-CN), '甘油三酸脂' (zh-CN), '三酸甘油脂 化学' (zh-CN), '化学检验项目' (zh-CN), '化学检验项目类' (zh-CN), '化学类' (zh-CN), '化学试验' (zh-CN), '非刺激耐受型化学检验项目' (zh-CN), '非刺激耐受型化学检验项目类' (zh-CN), '非刺激耐受型化学试验' (zh-CN), '非刺激耐受型化学试验类 可用数量表示的' (zh-CN), '定量性' (zh-CN), '数值型' (zh-CN), '数量型' (zh-CN), '连续数值型标尺 时刻' (zh-CN), '随机' (zh-CN), '随意' (zh-CN), '瞬间 血清或血浆 质量或摩尔浓度' (zh-CN), '质量或摩尔浓度(单位体积)' (zh-CN), '质量或物质的量浓度(单位体积)' (zh-CN), 'Juhuslik Kvantitatiivne Plasma Seerum Seerum või plasma' (et-EE), 'Trigl' (pt-BR), 'Triglycrides' (pt-BR), 'Trig' (pt-BR), 'Triglycerides' (pt-BR), 'Level' (pt-BR), 'Point in time' (pt-BR), 'Random' (pt-BR), 'SerPl' (pt-BR), 'SerPlas' (pt-BR), 'SerP' (pt-BR), 'Serum' (pt-BR), 'SR' (pt-BR), 'Plasma' (pt-BR), 'Pl' (pt-BR), 'Plsm' (pt-BR), 'Quantitative' (pt-BR), 'QNT' (pt-BR), 'Quant' (pt-BR), 'Quan' (pt-BR), 'Chemistry' (pt-BR), 'Chimica Concentrazione Sostanza o Massa Plasma Punto nel tempo (episodio) Siero Siero o Plasma' (it-IT), 'Количественный Массовая или Молярная Концентрация Плазма Сыворотка Сыворотка или Плазма Точка во времени' (ru-RU) or 'Момент' (ru-RU) (for the language(s) '--') (from Tx-Server)", "class" : "UNKNOWN", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "version" : "2.74", + "code" : "56445-0", + "display" : "Medication summary Doc" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Medication summary Document", + "code" : "56445-0", + "system" : "http://loinc.org", + "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "version" : "current", + "code" : "56445-0", + "display" : "Medication summary Doc" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"true", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Medication summary Document", + "code" : "56445-0", + "system" : "http://loinc.org", + "version" : "2.74", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "code" : "29463-7" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "Body weight", + "code" : "29463-7", + "system" : "http://loinc.org", + "version" : "2.74", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" diff --git a/pom.xml b/pom.xml index 9860fdb8f..2b1091b51 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 32.0.1-jre 6.4.1 - 1.4.2 + 1.4.3-SNAPSHOT 2.15.2 5.9.2 1.8.2 From c610e7a8b552182feb97d046b8246e1ccf236415 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 7 Sep 2023 22:01:13 +1000 Subject: [PATCH 06/10] Make Best Practice Recommendations work properly --- .../hl7/fhir/validation/BaseValidator.java | 7 - .../hl7/fhir/validation/ValidationEngine.java | 2 + .../fhir/validation/cli/model/CliContext.java | 21 ++- .../cli/services/ValidationService.java | 1 + .../hl7/fhir/validation/cli/utils/Params.java | 27 ++++ .../instance/InstanceValidator.java | 145 ++++++++++-------- .../type/StructureDefinitionValidator.java | 26 ++-- .../validation/tests/ValidationTests.java | 1 + 8 files changed, 141 insertions(+), 89 deletions(-) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index 8662bd357..ab0b79236 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -1365,11 +1365,4 @@ public class BaseValidator implements IValidationContextResourceLoader { return ok; } - protected boolean check(boolean ok) { -// if (!ok) { -// System.out.println("notok"); // #FIXME -// } - return ok; - } - } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 1ce1becfe..14d0ed7a6 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -217,6 +217,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP @Getter @Setter private HtmlInMarkdownCheck htmlInMarkdownCheck; @Getter @Setter private boolean allowDoubleQuotesInFHIRPath; @Getter @Setter private boolean checkIPSCodes; + @Getter @Setter private BestPracticeWarningLevel bestPracticeLevel; @Getter @Setter private Locale locale; @Getter @Setter private List igs = new ArrayList<>(); @Getter @Setter private List extensionDomains = new ArrayList<>(); @@ -856,6 +857,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP validator.setQuestionnaireMode(questionnaireMode); validator.setLevel(level); validator.setHtmlInMarkdownCheck(htmlInMarkdownCheck); + validator.setBestPracticeWarningLevel(bestPracticeLevel); validator.setAllowDoubleQuotesInFHIRPath(allowDoubleQuotesInFHIRPath); validator.setNoUnicodeBiDiControlChars(noUnicodeBiDiControlChars); validator.setDoImplicitFHIRPathStringConversion(doImplicitFHIRPathStringConversion); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index 27b2ac4b7..037bf6d38 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -9,6 +9,7 @@ import java.util.Objects; import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; import org.hl7.fhir.r5.utils.validation.BundleValidationRule; +import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.settings.FhirSettings; import org.hl7.fhir.validation.cli.services.ValidatorWatchMode; @@ -153,6 +154,9 @@ public class CliContext { @JsonProperty("watchSettleTime") private int watchSettleTime = 100; + @JsonProperty("bestPracticeLevel") + private BestPracticeWarningLevel bestPracticeLevel = BestPracticeWarningLevel.Warning; + @JsonProperty("map") public String getMap() { @@ -777,6 +781,7 @@ public class CliContext { Objects.equals(jurisdiction, that.jurisdiction) && Objects.equals(locations, that.locations) && Objects.equals(watchMode, that.watchMode) && + Objects.equals(bestPracticeLevel, that.bestPracticeLevel) && Objects.equals(watchScanDelay, that.watchScanDelay) && Objects.equals(watchSettleTime, that.watchSettleTime) ; } @@ -785,7 +790,7 @@ public class CliContext { public int hashCode() { return Objects.hash(doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT, - targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, watchMode, watchScanDelay, watchSettleTime, + targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes); } @@ -841,6 +846,7 @@ public class CliContext { ", allowDoubleQuotesInFHIRPath=" + allowDoubleQuotesInFHIRPath + ", checkIPSCodes=" + checkIPSCodes + ", watchMode=" + watchMode + + ", bestPracticeLevel=" + bestPracticeLevel + ", watchSettleTime=" + watchSettleTime + ", watchScanDelay=" + watchScanDelay + '}'; @@ -888,5 +894,16 @@ public class CliContext { this.watchSettleTime = watchSettleTime; } - + + @JsonProperty("bestPracticeLevel") + public BestPracticeWarningLevel getBestPracticeLevel() { + return bestPracticeLevel; + } + + @JsonProperty("bestPracticeLevel") + public CliContext setBestPracticeLevel(BestPracticeWarningLevel bestPracticeLevel) { + this.bestPracticeLevel = bestPracticeLevel; + return this; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index f358456ef..f7ef45a47 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -471,6 +471,7 @@ public class ValidationService { validationEngine.setNoUnicodeBiDiControlChars(cliContext.isNoUnicodeBiDiControlChars()); validationEngine.setNoInvariantChecks(cliContext.isNoInvariants()); validationEngine.setDisplayWarnings(cliContext.isDisplayWarnings()); + validationEngine.setBestPracticeLevel(cliContext.getBestPracticeLevel()); validationEngine.setCheckIPSCodes(cliContext.isCheckIPSCodes()); validationEngine.setWantInvariantInMessage(cliContext.isWantInvariantsInMessages()); validationEngine.setSecurityChecks(cliContext.isSecurityChecks()); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 047f262cd..1d92339bb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -7,6 +7,7 @@ import java.util.Locale; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; import org.hl7.fhir.r5.utils.validation.BundleValidationRule; +import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.validation.cli.model.CliContext; @@ -87,6 +88,8 @@ public class Params { public static final String TGT_LANG = "-tgt-lang"; public static final String ALLOW_DOUBLE_QUOTES = "-allow-double-quotes-in-fhirpath"; public static final String CHECK_IPS_CODES = "-check-ips-codes"; + public static final String BEST_PRACTICE = "-best-practice"; + public static final String RUN_TESTS = "-run-tests"; @@ -244,6 +247,13 @@ public class Params { cliContext.setHtmlInMarkdownCheck(HtmlInMarkdownCheck.fromCode(q)); } } + } else if (args[i].equals(BEST_PRACTICE)) { + if (i + 1 == args.length) + throw new Error("Specified "+BEST_PRACTICE+" without indicating mode"); + else { + String q = args[++i]; + cliContext.setBestPracticeLevel(readBestPractice(q)); + } } else if (args[i].equals(LOCALE)) { if (i + 1 == args.length) { throw new Error("Specified -locale without indicating locale"); @@ -439,6 +449,23 @@ public class Params { return cliContext; } + private static BestPracticeWarningLevel readBestPractice(String s) { + if (s == null) { + return BestPracticeWarningLevel.Warning; + } + switch (s.toLowerCase()) { + case "warning" : return BestPracticeWarningLevel.Warning; + case "error" : return BestPracticeWarningLevel.Error; + case "hint" : return BestPracticeWarningLevel.Hint; + case "ignore" : return BestPracticeWarningLevel.Ignore; + case "w" : return BestPracticeWarningLevel.Warning; + case "e" : return BestPracticeWarningLevel.Error; + case "h" : return BestPracticeWarningLevel.Hint; + case "i" : return BestPracticeWarningLevel.Ignore; + } + throw new Error("The best-practice level ''"+s+"'' is not valid"); + } + private static int readInteger(String name, String value) { if (!Utilities.isInteger(value)) { throw new Error("Unable to read "+value+" provided for '"+name+"' - must be an integer"); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index eb7d0b23d..b2f502a56 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -434,7 +434,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!ok && !record.isEmpty()) { ctxt.sliceNotes(url, record); } - return check(ok); + return ok; } @Override @@ -470,7 +470,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean noTerminologyChecks; private boolean hintAboutNonMustSupport; private boolean showMessagesFromReferences; - private BestPracticeWarningLevel bpWarnings; + private BestPracticeWarningLevel bpWarnings = BestPracticeWarningLevel.Warning; private String validationLanguage; private boolean baseOnly; private boolean noCheckAggregation; @@ -518,6 +518,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean logProgress; private CodingsObserver codingObserver; public List validatedContent; + public boolean testMode; public InstanceValidator(@Nonnull IWorkerContext theContext, @Nonnull IEvaluationContext hostServices, @Nonnull XVerExtensionManager xverManager) { super(theContext, xverManager, false); @@ -1030,7 +1031,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return check(ok); + return ok; } private boolean checkAttachment(List errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern) { @@ -1043,7 +1044,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), fixedSource, "hash", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), fixedSource, "title", focus, pattern) && ok; - return check(ok); + return ok; } // public API @@ -1128,9 +1129,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } catch (Exception e) { - return check(ok); + return ok; } } } @@ -1270,7 +1271,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return check(ok); + return ok; } private boolean checkCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, BooleanHolder bh) { @@ -1420,7 +1421,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } @@ -1556,7 +1557,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } private boolean checkTerminologyCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) { @@ -1656,7 +1657,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return check(ok); + return ok; } private CodeableConcept convertToCodeableConcept(Element element, StructureDefinition logical) { @@ -1761,7 +1762,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); } } - return check(ok); + return ok; } // private String describeValueSet(String url) { @@ -1805,7 +1806,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); } } - return check(ok); + return ok; } private boolean checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) { @@ -1840,7 +1841,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage()); } } - return check(ok); + return ok; } private String ccSummary(CodeableConcept cc) { @@ -1857,7 +1858,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) { @@ -1967,7 +1968,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return check(ok); + return ok; } private boolean isValueSet(String url) { @@ -1985,7 +1986,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkExtension(ValidatorHostContext hostContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException { @@ -2067,7 +2068,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url, pct, mode) && ok; } - return check(ok); + return ok; } private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) { @@ -2296,7 +2297,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat record.add(v); } } - return check(ok); + return ok; } else { warning(errors, "2023-07-03", IssueType.UNKNOWN, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.EXTENSION_CONTEXT_UNABLE_TO_CHECK_PROFILE, extUrl, expression, pu); @@ -2435,7 +2436,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return check(ok); + return ok; } private boolean checkHumanName(List errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) { @@ -2481,7 +2482,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - return check(ok); + return ok; } private boolean checkIdentifier(List errors, String path, Element element, ElementDefinition context) { @@ -2492,7 +2493,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String value = element.getNamedChildValue("value"); ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, value == null || isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE, value) && ok; } - return check(ok); + return ok; } private boolean checkIdentifier(List errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) { @@ -2503,14 +2504,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkPeriod(List errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) { boolean ok = true; ok = checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkPrimitive(ValidatorHostContext hostContext, List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException { @@ -2535,7 +2536,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } ok = rulePlural(errors, "2023-06-18", IssueType.INVALID, e.line(), e.col(), path, found, context.getValueAlternatives().size(), I18nConstants.PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE, context.getId(), profile.getVersionedUrl(), b.toString()) && ok; } - return check(ok); + return ok; } else { boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue()); if (hasBiDiControls) { @@ -2855,7 +2856,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // for nothing to check - return check(ok); + return ok; } private String getRegexFromType(String fhirType) { @@ -2982,7 +2983,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } private Set listExpectedCanonicalTypes(ElementDefinition context) { @@ -3096,7 +3097,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (charCount > 0 && charCount % 4 != 0) { ok = false; } - return check(ok); + return ok; } private boolean base64HasWhitespace(String theEncoded) { @@ -3157,7 +3158,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())) && ok; } } - return check(ok); + return ok; } private boolean checkUrls(List errors, Element e, String path, List list) { @@ -3174,7 +3175,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkUrls(errors, e, path, node.getChildNodes()) && ok; } } - return check(ok); + return ok; } private String checkValidUrl(String value) { @@ -3236,7 +3237,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkInnerNS(errors, e, path, node.getChildNodes()); } } - return check(ok); + return ok; } private boolean checkPrimitiveBinding(ValidatorHostContext hostContext, List errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) { @@ -3315,7 +3316,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (!noBindingMsgSuppressed) { hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2); } - return check(ok); + return ok; } private boolean isOkExtension(String value, ValueSet vs) { @@ -3432,7 +3433,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } private Decimal convertUcumValue(String value, String code, String minCode) { @@ -3532,7 +3533,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT); - return check(ok); + return ok; } // implementation @@ -3541,14 +3542,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat boolean ok = true; ok = checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkRatio(List errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) { boolean ok = true; ok = checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkReference(ValidatorHostContext hostContext, @@ -3824,7 +3825,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // todo: if the content is a resource, check that Reference.type is describing a resource - return check(ok); + return ok; } private boolean isSuspiciousReference(String url) { @@ -3956,7 +3957,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkReference(List errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern) { @@ -3965,7 +3966,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getTypeElement(), fixedSource, "type", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier"), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern) && ok; ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern) && ok; - return check(ok); + return ok; } private boolean checkTiming(List errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) { @@ -3980,7 +3981,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else { ok = false; } - return check(ok); + return ok; } private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) { @@ -4583,7 +4584,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) { - bpWarnings = value; + if (value == null) { + bpWarnings = BestPracticeWarningLevel.Warning; + } else { + bpWarnings = value; + } return this; } @@ -5204,7 +5209,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } // System.out.println("start: "+(System.currentTimeMillis()-st)+" ("+resource.fhirType()+")"); - return check(ok); + return ok; } private StructureDefinition lookupProfileReference(List errors, Element element, NodeStack stack, @@ -5309,7 +5314,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = vm.getLevel() != IssueSeverity.ERROR && vm.getLevel() != IssueSeverity.FATAL && ok; } } - return check(ok); + return ok; } if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getVersionedUrl())) { List localErrors = new ArrayList(); @@ -5329,7 +5334,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkSpecials(hostContext, errors, element, stack, checkSpecials, pct, mode) && ok; ok = validateResourceRules(errors, element, stack) && ok; } - return check(ok); + return ok; } public boolean checkSpecials(ValidatorHostContext hostContext, List errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode) { @@ -5461,7 +5466,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat i++; } } - return check(ok); + return ok; } private boolean validateCapabilityStatement(List errors, Element cs, NodeStack stack) { @@ -5487,7 +5492,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } iRest++; } - return check(ok); + return ok; } private boolean validateContains(ValidatorHostContext hostContext, List errors, String path, @@ -5511,7 +5516,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat hostContext, context.fhirType(), context.getId(), special, path, parentProfile.getUrl()); if (containedValidationPolicy.ignore()) { - return check(ok); + return ok; } String resourceName = element.getType(); @@ -5639,7 +5644,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } private String summariseErrors(List list) { @@ -5747,7 +5752,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, pct, mode) && ok; } vi.setValid(ok); - return check(ok); + return ok; } private SourcedChildDefinitions mergeChildLists(SourcedChildDefinitions source, SourcedChildDefinitions additional, String masterPath, String typePath) { @@ -5804,7 +5809,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } ok = checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true, pct, mode) && ok; } - return check(ok); + return ok; } private String time() { @@ -6096,7 +6101,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } private boolean isAbstractType(String type) { @@ -6225,7 +6230,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } } - return check(ok); + return ok; } public List assignChildren(ValidatorHostContext hostContext, List errors, StructureDefinition profile, Element resource, @@ -6500,7 +6505,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat errors.addAll(invErrors); } } - return check(ok); + return ok; } private boolean isInheritedProfile(List types, String source) { @@ -6577,6 +6582,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (inv.hasExtension(ToolingExtensions.EXT_BEST_PRACTICE) && ToolingExtensions.readBooleanExtension(inv, ToolingExtensions.EXT_BEST_PRACTICE)) { + msg = msg +" (Best Practice Recommendation)"; if (bpWarnings == BestPracticeWarningLevel.Hint) hint(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg); else if (/*bpWarnings == null || */ bpWarnings == BestPracticeWarningLevel.Warning) @@ -6589,7 +6595,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, invOK, msg); } } - return check(ok); + return ok; } private boolean validateObservation(List errors, Element element, NodeStack stack) { @@ -6602,7 +6608,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), performers.size() > 0, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER) && ok; ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_AN_EFFECTIVEDATETIME_OR_AN_EFFECTIVEPERIOD) && ok; - return check(ok); + return ok; } /* @@ -6611,11 +6617,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean validateResource(ValidatorHostContext hostContext, List errors, Element resource, Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack, PercentageTracker pct, ValidationMode mode) throws FHIRException { - boolean ok = true; - if ("Observation".equals(stack.getLiteralPath())) { - System.out.println("!"); // #FIXME - } - + boolean ok = true; // check here if we call validation policy here, and then change it to the new interface assert stack != null; assert resource != null; @@ -6670,24 +6672,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = false; } } - if (ok == hasErrors(errors)) { + if (testMode && ok == hasErrors(errors)) { throw new Error("ok is wrong. ok = "+ok+", errors = "+errorIds(stack.getLiteralPath(), ok, errors)); } - return check(ok); + return ok; } private String errorIds(String path, boolean ok, List errors) { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); for (ValidationMessage vm : errors) { -// if (vm.isError()) { + if (vm.isError()) { b.append(vm.getMessageId()); -// } + } } - System.out.println("OK = "+ok+" for "+path); - System.out.println("Errs? = "+hasErrors(errors)); - System.out.println("Errs = "+errors.toString()); String s = b.toString(); - System.out.println("Ids = "+s); + if (debug) { + System.out.println("OK = "+ok+" for "+path); + System.out.println("Errs = "+errors.toString()); + System.out.println("Ids = "+s); + } return s; } @@ -7042,6 +7045,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat this.warnOnDraftOrExperimental = warnOnDraftOrExperimental; return this; } + + public boolean isTestMode() { + return testMode; + } + + public void setTestMode(boolean testMode) { + this.testMode = testMode; + } } \ No newline at end of file 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 0739a7b35..6afc4500d 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 @@ -175,7 +175,7 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); ok = false; } - return check(ok); + return ok; } @@ -220,7 +220,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateObligationProfileElement(errors, element, stack.push(element, cc, null, null), base) && ok; cc++; } - return check(ok); + return ok; } private boolean validateObligationProfileElement(List errors, Element element, NodeStack push, StructureDefinition base) { @@ -269,7 +269,7 @@ public class StructureDefinitionValidator extends BaseValidator { I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL, id, child.getName()); } } - return check(ok); + return ok; } else { return false; } @@ -319,7 +319,7 @@ public class StructureDefinitionValidator extends BaseValidator { I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL_ON_BINDING, id, child.getName()); } } - return check(ok); + return ok; } private boolean checkExtensionContext(List errors, Element src, NodeStack stack) { @@ -356,7 +356,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION, type) && ok; } } - return check(ok); + return ok; } 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) { @@ -368,7 +368,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateElementDefinition(errors, elements, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint, invariantMap, rootPath, profileUrl, base) && ok; cc++; } - return check(ok); + 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, StructureDefinition base) { @@ -504,7 +504,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateElementDefinitionInvariant(errors, invariant, stack.push(invariant, cc, null, null), invariantMap, elements, element, element.getNamedChildValue("path"), rootPath, profileUrl, snapshot, base) && ok; cc++; } - return check(ok); + return ok; } private boolean validateElementDefinitionInvariant(List errors, Element invariant, NodeStack stack, Map invariantMap, List elements, Element element, @@ -565,7 +565,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return check(ok); + return ok; } private boolean haseHasInvariant(StructureDefinition base, String key) { @@ -798,7 +798,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return check(ok); + return ok; } private Set getListofBindableTypes(Set types) { @@ -842,7 +842,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return check(ok); + return ok; } private boolean validateProfileTypeOrTarget(List errors, Element profile, String code, NodeStack stack, String path) { @@ -880,7 +880,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return check(ok); + return ok; } private String getTypeCodeFromSD(StructureDefinition sd, String path) { @@ -937,7 +937,7 @@ public class StructureDefinitionValidator extends BaseValidator { } } } - return check(ok); + return ok; } private boolean checkIsModifierExtension(StructureDefinition t) { @@ -971,7 +971,7 @@ public class StructureDefinitionValidator extends BaseValidator { } else { ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code) && ok; } - return check(ok); + return ok; } private boolean isReferenceableTarget(StructureDefinition t) { diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index ef4d3a2b7..ed08bad1a 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -213,6 +213,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe InstanceValidator val = vCurr.getValidator(fmt); val.setWantCheckSnapshotUnchanged(true); val.getContext().setClientRetryCount(4); + val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore); val.setDebug(false); if (!VersionUtilities.isR5Plus(val.getContext().getVersion())) { val.getBaseOptions().setUseValueSetDisplays(true); From 304fa3f49359f077596763bef259700cb612d9bc Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 8 Sep 2023 04:21:14 +1000 Subject: [PATCH 07/10] Adjust tests for best-practice flag --- .../main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java | 1 - .../src/main/resources/Messages_de.properties | 1 - .../src/main/resources/Messages_es.properties | 4 ---- .../src/main/resources/Messages_nl.properties | 3 --- .../org/hl7/fhir/validation/tests/ValidationEngineTests.java | 4 ++-- 5 files changed, 2 insertions(+), 11 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 d036ce394..96bfaf460 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 @@ -27,7 +27,6 @@ public class I18nConstants { public static final String BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED = "BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED"; public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL = "Bundle_BUNDLE_Entry_NoProfile_EXPL"; public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE = "Bundle_BUNDLE_Entry_NoProfile_TYPE"; - public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES"; public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT = "Bundle_BUNDLE_Entry_Orphan_DOCUMENT"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_MESSAGE = "Bundle_BUNDLE_Entry_Orphan_MESSAGE"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages_de.properties b/org.hl7.fhir.utilities/src/main/resources/Messages_de.properties index cd241aec3..b3d975922 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages_de.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages_de.properties @@ -701,7 +701,6 @@ SD_MUST_HAVE_DERIVATION=StructureDefinition {0} muss eine Ableitung haben, da si VALIDATION_VAL_PROFILE_OTHER_VERSION=Profil ist f\u00fcr eine andere Version von FHIR ({0}) und wurde daher ignoriert VALIDATION_VAL_PROFILE_THIS_VERSION_OK=Profil f\u00fcr diese Version von FHIR - alles OK VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER=Profil ist f\u00fcr diese Version von FHIR, aber ist ein ung\u00fcltiger Typ {0} -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_other={0} Profile f\u00fcr {1} Ressource gefunden. Mehr als eines wird zur Zeit nicht unterst\u00fctzt. (Typ {2}: {3}) RENDER_BUNDLE_HEADER_ROOT=Bundle {0} vom Typ {1} RENDER_BUNDLE_HEADER_ENTRY=Entry {0} RENDER_BUNDLE_HEADER_ENTRY_URL=Entry {0} - fullUrl = {1} diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties b/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties index 3485b2106..210aa9aaf 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties @@ -653,10 +653,6 @@ SD_MUST_HAVE_DERIVATION = El recurso StructureDefinition {0} debe tener una deri VALIDATION_VAL_PROFILE_OTHER_VERSION = El perfil es para una versión diferente de FHIR ({0}) así que fue ignorado VALIDATION_VAL_PROFILE_THIS_VERSION_OK = El perfil es para esta versión de FHIR- Todo OK VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = El perfil es para esta versión de FHIR, pero es de tipo inválido {0} -#The following error cannot occur for a single item. _one case left intentionally blank. -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_one = -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_many = {0} perfiles encontrados para el recurso {1}. No hay soporte para más de uno por el monmento. (Type {2}: {3}) -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_other = {0} perfiles encontrados para el recurso {1}. No hay soporte para más de uno por el monmento. (Type {2}: {3}) RENDER_BUNDLE_HEADER_ROOT = Bundle {0} De tipo {1} RENDER_BUNDLE_HEADER_ENTRY = Entry {0} RENDER_BUNDLE_HEADER_ENTRY_URL = Entry {0} - FullURL = {1} diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties b/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties index 54557ef66..ea1faaa65 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages_nl.properties @@ -643,9 +643,6 @@ SD_MUST_HAVE_DERIVATION = StructureDefinition {0} moet een derivation bevatten, VALIDATION_VAL_PROFILE_OTHER_VERSION = Profiel is voor een andere versie van FHIR ({0}) dus wordt genegeerd VALIDATION_VAL_PROFILE_THIS_VERSION_OK = Profiel voor deze versie van FHIR - alles in orde VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = Profiel is voor deze versie van FHIR, maar heeft een onjuist type {0} -#The following error cannot occur for a single item. _one case left intentionally blank. -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_one = -BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_other = {0} profielen gevonden voor {1} resource. Meer dan een wordt momenteel niet ondersteund. (Type {2}: {3}) RENDER_BUNDLE_HEADER_ROOT = Bundle {0} van type {1} RENDER_BUNDLE_HEADER_ENTRY = Entry {0} RENDER_BUNDLE_HEADER_ENTRY_URL = Entry {0} - Full URL = {1} diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java index e4781ed60..4625078b7 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationEngineTests.java @@ -192,7 +192,7 @@ public class ValidationEngineTests { int w = warnings(op); int h = hints(op); Assertions.assertEquals(1, e); - Assertions.assertEquals(0, w); + Assertions.assertEquals(2, w); Assertions.assertEquals(1, h); assertTrue(logger.verifyHasNoRequests(), "Unexpected request to TX server"); if (!TestUtilities.silent) @@ -270,7 +270,7 @@ public class ValidationEngineTests { int w = warnings(op); int h = hints(op); Assertions.assertEquals(0, e); - Assertions.assertEquals(2, w); + Assertions.assertEquals(4, w); Assertions.assertEquals(0, h); assertTrue(logger.verifyHasNoRequests(), "Unexpected request to TX server"); if (!TestUtilities.silent) From ee6ec0e7d1fc34ee0467150d0125e4da668355a5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 8 Sep 2023 04:25:27 +1000 Subject: [PATCH 08/10] release notes --- RELEASE_NOTES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7b06c6ab5..ca383faf6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,8 @@ ## Validator Changes -* no changes +* Fix bug preventing Best Practice invariants being reported on, and add -best-practice parameter +* Fix issue not validating bundles when there are multiple profiles on entry.resource ## Other code changes -* no changes \ No newline at end of file +* Fixes for minor bugs discovered testing the XIG From 6b38a730e9d31c8d3b608160170b6fbdccb59dd2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 8 Sep 2023 07:35:11 +1000 Subject: [PATCH 09/10] SHC signature verification --- .../hl7/fhir/r5/elementmodel/SHCParser.java | 202 +++++++++++++++--- .../hl7/fhir/r5/elementmodel/SHLParser.java | 2 +- .../org.hl7.fhir.validation/4.0.1/cvx.cache | 73 ++++++- 3 files changed, 238 insertions(+), 39 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java index 0760a2531..2f4b9c510 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java @@ -5,9 +5,15 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -19,10 +25,12 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; +import org.hl7.fhir.r5.elementmodel.SHCParser.SHCSignedJWT; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.json.JsonException; import org.hl7.fhir.utilities.json.model.JsonArray; import org.hl7.fhir.utilities.json.model.JsonElement; import org.hl7.fhir.utilities.json.model.JsonElementType; @@ -33,6 +41,20 @@ import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.ECDSAVerifier; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.source.*; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; +import com.nimbusds.jose.proc.JWSKeySelector; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jose.util.JSONObjectUtils; +import com.nimbusds.jwt.*; +import com.nimbusds.jwt.proc.*; /** * this class is actually a smart health cards validator. * It's going to parse the JWT and assume that it contains @@ -52,7 +74,7 @@ public class SHCParser extends ParserBase { private JsonParser jsonParser; private List types = new ArrayList<>(); - + public SHCParser(IWorkerContext context) { super(context); jsonParser = new JsonParser(context); @@ -64,24 +86,24 @@ public class SHCParser extends ParserBase { List res = new ArrayList<>(); NamedElement shc = new NamedElement("shc", "json", content); res.add(shc); - + String src = TextFile.streamToString(stream).trim(); List list = new ArrayList<>(); String pfx = null; if (src.startsWith("{")) { JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(src); if (checkProperty(shc.getErrors(), json, "$", "verifiableCredential", true, "Array")) { - pfx = "verifiableCredential"; - JsonArray arr = json.getJsonArray("verifiableCredential"); - int i = 0; - for (JsonElement e : arr) { - if (!(e instanceof JsonPrimitive)) { - logError(shc.getErrors(), ValidationMessage.NO_RULE_DATE, line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+e.type().toName(), IssueSeverity.ERROR); - } else { - list.add(e.asString()); - } - i++; - } + pfx = "verifiableCredential"; + JsonArray arr = json.getJsonArray("verifiableCredential"); + int i = 0; + for (JsonElement e : arr) { + if (!(e instanceof JsonPrimitive)) { + logError(shc.getErrors(), ValidationMessage.NO_RULE_DATE, line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+e.type().toName(), IssueSeverity.ERROR); + } else { + list.add(e.asString()); + } + i++; + } } else { return res; } @@ -102,8 +124,6 @@ public class SHCParser extends ParserBase { checkNamedProperties(shc.getErrors(), jwt.getPayload(), prefix+"payload", "iss", "nbf", "vc"); checkProperty(shc.getErrors(), jwt.getPayload(), prefix+"payload", "iss", true, "String"); - logError(shc.getErrors(), ValidationMessage.NO_RULE_DATE, 1, 1, prefix+"JWT", IssueType.INFORMATIONAL, "The FHIR Validator does not check the JWT signature "+ - "(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().asString("iss")+"')", IssueSeverity.INFORMATION); checkProperty(shc.getErrors(), jwt.getPayload(), prefix+"payload", "nbf", true, "Number"); JsonObject vc = jwt.getPayload().getJsonObject("vc"); if (vc == null) { @@ -167,7 +187,7 @@ public class SHCParser extends ParserBase { } return null; } - + private boolean checkProperty(List errors, JsonObject obj, String path, String name, boolean required, String type) { JsonElement e = obj.get(name); @@ -193,7 +213,7 @@ public class SHCParser extends ParserBase { } } } - + private int line(JsonElement e) { return e.getStart().getLine(); } @@ -209,12 +229,12 @@ public class SHCParser extends ParserBase { // because then we'd have to try to sign, and we're just not going to be doing that from the element model } - + public static class JWT { private JsonObject header; private JsonObject payload; - + public JsonObject getHeader() { return header; } @@ -232,7 +252,7 @@ public class SHCParser extends ParserBase { private static final int BUFFER_SIZE = 1024; public static final String CURRENT_PACKAGE = "hl7.fhir.uv.shc-vaccination#0.6.2"; private static final int MAX_ALLOWED_SHC_LENGTH = 1195; - + // todo: deal with chunking public static String decodeQRCode(String src) { StringBuilder b = new StringBuilder(); @@ -273,9 +293,133 @@ public class SHCParser extends ParserBase { payloadJson = inflate(payloadJson); } res.payload = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TextFile.bytesToString(payloadJson), true); + + checkSignature(jwt, res, errors, "jwt", org.hl7.fhir.utilities.json.parser.JsonParser.compose(res.payload)); return res; } - + + private void checkSignature(String jwt, JWT res, List errors, String name, String jsonPayload) { + String iss = res.payload.asString("iss"); + if (iss != null) { // reported elsewhere + if (!iss.startsWith("https://")) { + logError(errors, "2023-09-08", 1, 1, name, IssueType.NOTFOUND, "JWT iss '"+iss+"' must start with https://", IssueSeverity.ERROR); + } + if (iss.endsWith("/")) { + logError(errors, "2023-09-08", 1, 1, name, IssueType.NOTFOUND, "JWT iss '"+iss+"' must not have trailing /", IssueSeverity.ERROR); + iss = iss.substring(0, iss.length()-1); + } + String url = Utilities.pathURL(iss, "/.well-known/jwks.json"); + JsonObject jwks = null; + try { + jwks = org.hl7.fhir.utilities.json.parser.JsonParser.parseObjectFromUrl(url); + } catch (Exception e) { + logError(errors, "2023-09-08", 1, 1, name, IssueType.NOTFOUND, "Unable to verify the signature, because unable to retrieve JWKS from "+url+": "+e.getMessage(), IssueSeverity.ERROR); + } + if (jwks != null) { + verifySignature(jwt, errors, name, iss, url, org.hl7.fhir.utilities.json.parser.JsonParser.compose(jwks)); + } + + // TODO Auto-generated method stub + + // + // logError(shc.getErrors(), ValidationMessage.NO_RULE_DATE, 1, 1, prefix+"JWT", IssueType.INFORMATIONAL, "The FHIR Validator does not check the JWT signature "+ + // "(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().asString("iss")+"')", IssueSeverity.INFORMATION); + } + + } + + public class SHCSignedJWT extends com.nimbusds.jwt.SignedJWT { + private static final long serialVersionUID = 1L; + private JWTClaimsSet claimsSet; + + public SHCSignedJWT(SignedJWT jwtO, String jsonPayload) throws ParseException { + super(jwtO.getParsedParts()[0], jwtO.getParsedParts()[1], jwtO.getParsedParts()[2]); + Map json = JSONObjectUtils.parse(jsonPayload); + claimsSet = JWTClaimsSet.parse(json); + } + + public JWTClaimsSet getJWTClaimsSet() { + return claimsSet; + } + } + + private void verifySignature(String jwt, List errors, String name, String iss, String url, String jwks) { + try { + // Parse the JWS token + JWSObject jwsObject = JWSObject.parse(jwt); + + // Extract header details + JWSHeader header = jwsObject.getHeader(); + validateHeader(header); + + // Decompress the payload + byte[] decodedPayload = jwsObject.getPayload().toBytes(); + String decompressedPayload = decompress(decodedPayload); + + // Extract issuer from the payload + JsonObject rootNode = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(decompressedPayload); + String issuer = rootNode.asString("iss"); + + // Fetch the public key + JWKSet jwkSet = JWKSet.parse(jwks); + JWK publicKey = jwkSet.getKeyByKeyId(header.getKeyID()); + + // Verify the JWS token + JWSVerifier verifier = new ECDSAVerifier((ECKey) publicKey); + if (jwsObject.verify(verifier)) { + String vciName = getVCIIssuer(errors, issuer); + if (vciName == null) { + logError(errors, "2023-09-08", 1, 1, name, IssueType.BUSINESSRULE, "The signature is valid, but the issuer "+issuer+" is not a trusted issuer", IssueSeverity.WARNING); + } else { + logError(errors, "2023-09-08", 1, 1, name, IssueType.INFORMATIONAL, "The signature is valid, signed by the trusted issuer '"+vciName+"' ("+issuer+")", IssueSeverity.INFORMATION); + } + } else { + logError(errors, "2023-09-08", 1, 1, name, IssueType.BUSINESSRULE, "The signature is not valid", IssueSeverity.ERROR); + } + } catch (Exception e) { + logError(errors, "2023-09-08", 1, 1, name, IssueType.NOTFOUND, "Error validating signature: "+e.getMessage(), IssueSeverity.ERROR); + } + } + + private static void validateHeader(JWSHeader header) { + if (!"ES256".equals(header.getAlgorithm().getName())) { + throw new IllegalArgumentException("Invalid alg in JWS header. Expected ES256."); + } + if (!header.getCustomParam("zip").equals("DEF")) { + throw new IllegalArgumentException("Invalid zip in JWS header. Expected DEF."); + } + } + + private static String decompress(byte[] compressed) throws Exception { + Inflater inflater = new Inflater(true); + inflater.setInput(compressed); + + byte[] buffer = new byte[1024]; + int length; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(compressed.length)) { + while (!inflater.finished()) { + length = inflater.inflate(buffer); + outputStream.write(buffer, 0, length); + } + return outputStream.toString(StandardCharsets.UTF_8.name()); + } + } + + + private String getVCIIssuer(List errors, String issuer) { + try { + JsonObject vci = org.hl7.fhir.utilities.json.parser.JsonParser.parseObjectFromUrl("https://raw.githubusercontent.com/the-commons-project/vci-directory/main/vci-issuers.json"); + for (JsonObject j : vci.getJsonObjects("participating_issuers")) { + if (issuer.equals(j.asString("iss"))) { + return j.asString("name"); + } + } + } catch (Exception e) { + logError(errors, "2023-09-08", 1, 1, "vci", IssueType.NOTFOUND, "Unable to retrieve/read VCI Trusted Issuer list: "+e.getMessage(), IssueSeverity.WARNING); + } + return null; + } + static String[] splitToken(String token) { String[] parts = token.split("\\."); if (parts.length == 2 && token.endsWith(".")) { @@ -287,21 +431,21 @@ public class SHCParser extends ParserBase { } return parts; } - + public static final byte[] inflate(byte[] data) throws IOException, DataFormatException { final Inflater inflater = new Inflater(true); inflater.setInput(data); try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length)) { - byte[] buffer = new byte[BUFFER_SIZE]; - while (!inflater.finished()) - { - final int count = inflater.inflate(buffer); - outputStream.write(buffer, 0, count); - } + byte[] buffer = new byte[BUFFER_SIZE]; + while (!inflater.finished()) + { + final int count = inflater.inflate(buffer); + outputStream.write(buffer, 0, count); + } - return outputStream.toByteArray(); + return outputStream.toByteArray(); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java index 2ed4b8e07..065c3553e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHLParser.java @@ -359,7 +359,7 @@ public class SHLParser extends ParserBase { long epochSecs = Long.valueOf(v); LocalDateTime date = LocalDateTime.ofEpochSecond(epochSecs, 0, ZoneOffset.UTC); LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC); - Duration duration = Duration.between(now, date); + Duration duration = Duration.between(date, now); if (date.isBefore(now)) { logError(errors, "2023-08-31", p.getValue().getStart().getLine(), p.getValue().getStart().getCol(), "shl."+p.getName(), diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cvx.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cvx.cache index 3f595bde8..b1d6e1ffc 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cvx.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cvx.cache @@ -119,7 +119,6 @@ v: { "code" : "115", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -141,7 +140,6 @@ v: { "code" : "10", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -163,7 +161,6 @@ v: { "code" : "85", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -185,7 +182,6 @@ v: { "code" : "25", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -207,7 +203,6 @@ v: { "code" : "37", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -229,7 +224,6 @@ v: { "code" : "185", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -251,7 +245,6 @@ v: { "code" : "150", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -273,7 +266,6 @@ v: { "code" : "207", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -295,7 +287,6 @@ v: { "code" : "171", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", - "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" } @@ -317,6 +308,70 @@ v: { "code" : "88", "system" : "http://hl7.org/fhir/sid/cvx", "version" : "20210406", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://hl7.org/fhir/sid/cvx", + "code" : "210" +}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "SARS-COV-2 (COVID-19) vaccine, vector non-replicating, recombinant spike protein-ChAdOx1, preservative free, 0.5 mL", + "code" : "210", + "system" : "http://hl7.org/fhir/sid/cvx", + "version" : "20210406", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://hl7.org/fhir/sid/cvx", + "code" : "210" +}, "url": "http://hl7.org/fhir/uv/shc-vaccination/ValueSet/vaccine-cvx", "version": "0.6.2", "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "SARS-COV-2 (COVID-19) vaccine, vector non-replicating, recombinant spike protein-ChAdOx1, preservative free, 0.5 mL", + "code" : "210", + "system" : "http://hl7.org/fhir/sid/cvx", + "version" : "20210406", + "unknown-systems" : "", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://hl7.org/fhir/sid/cvx", + "code" : "207" +}, "url": "http://hl7.org/fhir/uv/shc-vaccination/ValueSet/vaccine-cvx", "version": "0.6.2", "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"CHECK_MEMERSHIP_ONLY", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "display" : "SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 100 mcg/0.5mL dose", + "code" : "207", + "system" : "http://hl7.org/fhir/sid/cvx", + "version" : "20210406", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" From 307623fd058d7dbd7421fdeda9e53f45cc3e2b82 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 8 Sep 2023 09:00:05 +1000 Subject: [PATCH 10/10] Fix failing tests due to jvm difference --- .../src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java index 2f4b9c510..11acb9ed8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java @@ -313,7 +313,8 @@ public class SHCParser extends ParserBase { try { jwks = org.hl7.fhir.utilities.json.parser.JsonParser.parseObjectFromUrl(url); } catch (Exception e) { - logError(errors, "2023-09-08", 1, 1, name, IssueType.NOTFOUND, "Unable to verify the signature, because unable to retrieve JWKS from "+url+": "+e.getMessage(), IssueSeverity.ERROR); + logError(errors, "2023-09-08", 1, 1, name, IssueType.NOTFOUND, "Unable to verify the signature, because unable to retrieve JWKS from "+url+": "+ + e.getMessage().replace("Connection refused (Connection refused)", "Connection refused"), IssueSeverity.ERROR); } if (jwks != null) { verifySignature(jwt, errors, name, iss, url, org.hl7.fhir.utilities.json.parser.JsonParser.compose(jwks));