From 21848fc9af1c355a0b9f5aab6381623fb79a3c44 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 5 Oct 2022 21:16:57 +1100 Subject: [PATCH 1/6] Fix handling of Coding Validation when code system is unknown --- .../configuration/CodeableConcept.java | 7 ++- .../hl7/fhir/r5/elementmodel/XmlParser.java | 3 +- .../hl7/fhir/r5/model/CodeableConcept.java | 5 +++ .../r5/terminologies/ValueSetChecker.java | 17 +++++++- .../terminologies/ValueSetCheckerSimple.java | 43 +++++++++++-------- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/org.hl7.fhir.core.generator/configuration/CodeableConcept.java b/org.hl7.fhir.core.generator/configuration/CodeableConcept.java index c1ff36027..ccf28700d 100644 --- a/org.hl7.fhir.core.generator/configuration/CodeableConcept.java +++ b/org.hl7.fhir.core.generator/configuration/CodeableConcept.java @@ -93,4 +93,9 @@ public boolean hasCoding(String system, String code) { public void addCoding(String system, String code, String display) { getCoding().add(new Coding(system, code, display)); } - \ No newline at end of file + + @Override + public String toString() { + return hasCoding() ? getCoding().toString() : "["+getText()+"]"; + } + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java index e0d1ec912..0c5a0ce9c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java @@ -365,8 +365,9 @@ public class XmlParser extends ParserBase { } else ok = ok || (attr.getLocalName().equals("schemaLocation")); // xsi:schemalocation allowed for non FHIR content ok = ok || (hasTypeAttr(element) && attr.getLocalName().equals("type") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())); // xsi:type allowed if element says so - if (!ok) + if (!ok) { logError(line(node, false), col(node, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR); + } } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java index 1705d65a3..a75dc1ed0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java @@ -404,6 +404,11 @@ public boolean hasCoding(String system, String code) { public void addCoding(String system, String code, String display) { getCoding().add(new Coding(system, code, display)); } + + @Override + public String toString() { + return hasCoding() ? getCoding().toString() : "["+getText()+"]"; + } // end addition diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java index d51437863..85df79322 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java @@ -1,5 +1,6 @@ package org.hl7.fhir.r5.terminologies; +import java.util.ArrayList; import java.util.List; /* @@ -34,10 +35,24 @@ import java.util.List; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ETooCostly; +import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; import org.hl7.fhir.r5.utils.EOperationOutcome; public interface ValueSetChecker { - Boolean codeInValueSet(String system, String code, List warnings) throws ETooCostly, EOperationOutcome, Exception; + public static class ValidationProcessInfo { + private TerminologyServiceErrorClass err; + private List warnings = new ArrayList<>(); + public TerminologyServiceErrorClass getErr() { + return err; + } + public void setErr(TerminologyServiceErrorClass err) { + this.err = err; + } + public List getWarnings() { + return warnings; + } + } + Boolean codeInValueSet(String system, String code, ValidationProcessInfo info) throws ETooCostly, EOperationOutcome, Exception; } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java index 0bcbe02fb..2f62fdb26 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java @@ -61,6 +61,7 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r5.terminologies.ValueSetChecker.ValidationProcessInfo; import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; @@ -129,11 +130,11 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe public ValidationResult validateCode(CodeableConcept code) throws FHIRException { // first, we validate the codings themselves List errors = new ArrayList(); - List warnings = new ArrayList(); + ValidationProcessInfo info = new ValidationProcessInfo(); if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { for (Coding c : code.getCoding()) { if (!c.hasSystem()) { - warnings.add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); + info.getWarnings().add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); } CodeSystem cs = resolveCodeSystem(c.getSystem()); ValidationResult res = null; @@ -145,7 +146,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (!res.isOk()) { errors.add(res.getMessage()); } else if (res.getMessage() != null) { - warnings.add(res.getMessage()); + info.getWarnings().add(res.getMessage()); } } } @@ -153,7 +154,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { Boolean result = false; for (Coding c : code.getCoding()) { - Boolean ok = codeInValueSet(c.getSystem(), c.getCode(), warnings); + Boolean ok = codeInValueSet(c.getSystem(), c.getCode(), info); if (ok == null && result == false) { result = null; } else if (ok) { @@ -162,15 +163,15 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } } if (result == null) { - warnings.add(0, context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); + info.getWarnings().add(0, context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); } else if (!result) { errors.add(0, context.formatMessage(I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); } } if (errors.size() > 0) { return new ValidationResult(IssueSeverity.ERROR, errors.toString()); - } else if (warnings.size() > 0) { - return new ValidationResult(IssueSeverity.WARNING, warnings.toString()); + } else if (info.getWarnings().size() > 0) { + return new ValidationResult(IssueSeverity.WARNING, info.getWarnings().toString()); } else { ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); cd.setDisplay(foundCoding.getDisplay()); @@ -251,19 +252,24 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe inInclude = checkInclude(code); } - List warnings = new ArrayList<>(); + ValidationProcessInfo info = new ValidationProcessInfo(); // then, if we have a value set, we check it's in the value set if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { if ((res==null || res.isOk())) { - Boolean ok = codeInValueSet(system, code.getCode(), warnings); + Boolean ok = codeInValueSet(system, code.getCode(), info); if (ok == null || !ok) { if (res == null) { res = new ValidationResult((IssueSeverity) null, null); } - if (!inExpansion && !inInclude) { - if (warnings != null) { - res.setMessage("Not in value set "+valueset.getUrl()+" ("+warnings+")").setSeverity(IssueSeverity.ERROR); + if (info.getErr() != null) { + res.setErrorClass(info.getErr()); + } + if (ok == null) { + res.setMessage("Unable to check whether code is in value set "+valueset.getUrl()+": "+info.getWarnings()).setSeverity(IssueSeverity.WARNING); + } else if (!inExpansion && !inInclude) { + if (!info.getWarnings().isEmpty()) { + res.setMessage("Not in value set "+valueset.getUrl()+": "+info.getWarnings()).setSeverity(IssueSeverity.ERROR); } else { res.setMessage("Not in value set "+valueset.getUrl()).setSeverity(IssueSeverity.ERROR); } @@ -640,7 +646,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } @Override - public Boolean codeInValueSet(String system, String code, List warnings) throws FHIRException { + public Boolean codeInValueSet(String system, String code, ValidationProcessInfo info) throws FHIRException { if (valueset == null) { return false; } @@ -651,7 +657,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } else if (valueset.hasCompose()) { int i = 0; for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { - Boolean ok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + Boolean ok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, info); i++; if (ok == null && result == false) { result = null; @@ -662,7 +668,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } i = valueset.getCompose().getInclude().size(); for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) { - Boolean nok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + Boolean nok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, info); i++; if (nok == null && result == false) { result = null; @@ -675,7 +681,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe return result; } - private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, List warnings) throws FHIRException { + private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, ValidationProcessInfo info) throws FHIRException { boolean ok = true; if (vsi.hasValueSet()) { @@ -721,8 +727,9 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe vs.getCompose().addInclude(vsi); ValidationResult res = context.validateCode(options.noClient(), new Coding(system, code, null), vs); if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { - if (warnings != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { - warnings.add(context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system)); + if (info != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { + info.getWarnings().add(context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system)); + info.setErr(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); } return null; } From ae8bc3502f38251a97fe7c1e21028eec6718de25 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 5 Oct 2022 21:17:40 +1100 Subject: [PATCH 2/6] fix FHIRPath .is(type) to handle abstract types --- .../java/org/hl7/fhir/r5/utils/FHIRPathEngine.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 705a3a449..4fbafabb7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -4503,7 +4503,18 @@ public class FHIRPathEngine { return makeBoolean(false); } } else if (ns.equals("FHIR")) { - return makeBoolean(n.equals(focus.get(0).fhirType())); + if (n.equals(focus.get(0).fhirType())) { + return makeBoolean(true); + } else { + StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType()); + while (sd != null) { + if (n.equals(sd.getType())) { + return makeBoolean(true); + } + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return makeBoolean(false); + } } else { return makeBoolean(false); } From efd8e07b9de123486ab0cde3dd298e34564faf26 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 5 Oct 2022 21:17:52 +1100 Subject: [PATCH 3/6] fix bugs in graphql generation --- .../fhir/r5/utils/GraphQLSchemaGenerator.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java index a637231dd..ee0a1f6e2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java @@ -103,6 +103,9 @@ public class GraphQLSchemaGenerator { if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { tl.put(sd.getName(), sd); } + if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() != TypeDerivationRule.CONSTRAINT && sd.getAbstract()) { + tl.put(sd.getName(), sd); + } } writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n"); writer.write("# FHIR Defined Primitive types\r\n"); @@ -290,16 +293,17 @@ public class GraphQLSchemaGenerator { } private void generateType(Map existingTypeNames, Writer writer, StructureDefinition sd, EnumSet operations) throws IOException { - if (sd.getAbstract()) { - return; - } - if (operations.contains(FHIROperationType.READ) || operations.contains(FHIROperationType.SEARCH)) { List list = new ArrayList<>(); StringBuilder b = new StringBuilder(); list.add(b); b.append("type "); b.append(sd.getName()); + StructureDefinition sdp = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + if (sdp != null) { + b.append(" implements "); + b.append(sdp.getType()); + } b.append(" {\r\n"); ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); generateProperties(existingTypeNames, list, b, sd.getName(), sd, ed, "type", ""); @@ -370,7 +374,13 @@ public class GraphQLSchemaGenerator { if (suffix) b.append(Utilities.capitalize(typeDetails.getWorkingCode())); b.append(": "); - b.append(n); + if (!child.getMax().equals("1")) { + b.append("["); + b.append(n); + b.append("]"); + } else { + b.append(n); + } if (!child.getPath().endsWith(".id")) { b.append(" _"); b.append(tail(child.getPath(), suffix)); From c7ef0a3ac61b281df2d372a43d61431509569f9f Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 5 Oct 2022 21:19:30 +1100 Subject: [PATCH 4/6] Add support for http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies to validator --- .../hl7/fhir/r5/utils/ToolingExtensions.java | 1 + .../fhir/utilities/i18n/I18nConstants.java | 2 + .../src/main/resources/Messages.properties | 2 + .../instance/InstanceValidator.java | 100 +++++++++++++----- .../validation/tests/ValidationTests.java | 8 +- 5 files changed, 82 insertions(+), 31 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 60b74d0a6..8e453d43c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -205,6 +205,7 @@ public class ToolingExtensions { public static final String EXT_REND_MD = "http://hl7.org/fhir/StructureDefinition/rendering-markdown"; public static final String EXT_CAP_STMT_EXPECT = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation"; public static final String EXT_ED_HEIRARCHY = "http://hl7.org/fhir/StructureDefinition/elementdefinition-heirarchy"; + public static final String EXT_SD_DEPENDENCY = "http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies"; // in the tooling IG public static final String EXT_BINDING_ADDITIONAL = "http://hl7.org/fhir/tools/StructureDefinition/additional-binding"; 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 a226608f3..7db656b37 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 @@ -611,6 +611,7 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_SIGNPOST = "VALIDATION_VAL_PROFILE_SIGNPOST"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META"; + public static final String VALIDATION_VAL_PROFILE_SIGNPOST_DEP = "VALIDATION_VAL_PROFILE_SIGNPOST_DEP"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM = "VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM"; public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder"; public static final String VALIDATION_VAL_PROFILE_OTHER_VERSION = "VALIDATION_VAL_PROFILE_OTHER_VERSION"; @@ -622,6 +623,7 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2"; public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile"; + public static final String VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED = "VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 74112502d..75985cd76 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -236,6 +236,7 @@ Validation_VAL_Profile_Unknown = Profile reference ''{0}'' has not been checked VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY = Profile reference ''{0}'' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles VALIDATION_VAL_PROFILE_UNKNOWN_ERROR = Profile reference ''{0}'' has not been checked because it is unknown, and fetching it resulted in the error {1} Validation_VAL_Unknown_Profile = Unknown profile {0} +VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED = Profile {1} identifies {2} as a dependency (using the extension http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies), but this profile could not be found XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML (''{0}'' on ''{1}'') XHTML_XHTML_Element_Illegal = Illegal element name in the XHTML (''{0}'') XHTML_XHTML_NS_InValid = Wrong namespace on the XHTML (''{0}'', should be ''{1}'') @@ -536,6 +537,7 @@ VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN = Global Profile reference ''{0}'' from IG VALIDATION_VAL_PROFILE_SIGNPOST_BASE = Validate resource against profile VALIDATION_VAL_PROFILE_SIGNPOST = Validate resource against profile {0} VALIDATION_VAL_PROFILE_SIGNPOST_META = Validate resource against profile {0} (per meta) +VALIDATION_VAL_PROFILE_SIGNPOST_DEP = Validate resource against profile {0} (per http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies in {1}) VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM = Validate resource against profile {0} - provided as bundle param VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = Validate resource against profile {0} - a global profile in {1} ERROR_GENERATING_SNAPSHOT = Error generating Snapshot: {0} (this usually arises from a problem in the differential) 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 0dc80ac8d..27b1d119e 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 @@ -818,15 +818,31 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat setParents(element); long t = System.nanoTime(); + NodeStack stack = new NodeStack(context, path, element, validationLanguage); if (profiles == null || profiles.isEmpty()) { - validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds(), null); + validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, stack.resetIds(), null); } else { + int i = 0; + while (i < profiles.size()) { + StructureDefinition sd = profiles.get(i); + if (sd.hasExtension(ToolingExtensions.EXT_SD_DEPENDENCY)) { + for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_DEPENDENCY)) { + StructureDefinition dep = context.fetchResource( StructureDefinition.class, ext.getValue().primitiveValue()); + if (dep == null) { + warning(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED, ext.getValue().primitiveValue(), sd.getUrl()); + } else if (!profiles.contains(dep)) { + profiles.add(dep); + } + } + } + i++; + } for (StructureDefinition defn : profiles) { - validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds(), null); + validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null); } } if (hintAboutNonMustSupport) { - checkElementUsage(errors, element, new NodeStack(context, path, element, validationLanguage)); + checkElementUsage(errors, element, stack); } errors.removeAll(messagesToRemove); timeTracker.overall(t); @@ -1217,7 +1233,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkBindings(errors, path, element, stack, valueset, nextCoding); } } - timeTracker.tx(t, "vc "+DataRenderer.display(context, cc)); + timeTracker.tx(t, "vc "+cc.toString()); } } } catch (Exception e) { @@ -1692,6 +1708,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode); } } + } else if (vr != null && vr.getMessage() != null) { + txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } } catch (Exception e) { if (STACK_TRACE) e.printStackTrace(); @@ -3713,7 +3731,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private String getErrorMessage(String message) { - return message != null ? " (error message = " + message + ")" : ""; + return message != null ? " (error message = " + message + ")" : ""; } public boolean isSuppressLoincSnomedMessages() { @@ -4517,28 +4535,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) { warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue()); } else { - sd = null; - String url = profile.primitiveValue(); - CanonicalResourceLookupResult cr = crLookups.get(url); - if (cr != null) { - if (cr.error != null) { - warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, url, cr.error); - } else { - sd = (StructureDefinition) cr.resource; - } - } else { - try { - sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, url); - crLookups.put(url, new CanonicalResourceLookupResult(sd)); - } catch (Exception e) { - if (STACK_TRACE) { e.printStackTrace(); } - crLookups.put(url, new CanonicalResourceLookupResult(e.getMessage())); - warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage()); - } - if (sd != null) { - context.cacheResource(sd); - } - } + sd = lookupProfileReference(errors, element, stack, i, profile, sd); } } if (sd != null) { @@ -4553,6 +4550,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (pctOwned) { pct.done(); } + if (sd.hasExtension(ToolingExtensions.EXT_SD_DEPENDENCY)) { + for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_DEPENDENCY)) { + StructureDefinition sdi = context.fetchResource(StructureDefinition.class, ext.getValue().primitiveValue()); + if (sdi == null) { + warning(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED, ext.getValue().primitiveValue(), sd.getUrl()); + } else { + if (crumbTrails) { + element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_DEP, sdi.getUrl(), sd.getUrl())); + } + stack.resetIds(); + if (pctOwned) { + pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sdi.getUrl(), logProgress); + } + startInner(hostContext, errors, resource, element, sdi, stack, false, pct); + if (pctOwned) { + pct.done(); + } + + } + } + } } } } @@ -4583,6 +4601,32 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // System.out.println("start: "+(System.currentTimeMillis()-st)+" ("+resource.fhirType()+")"); } + private StructureDefinition lookupProfileReference(List errors, Element element, NodeStack stack, + int i, Element profile, StructureDefinition sd) { + String url = profile.primitiveValue(); + CanonicalResourceLookupResult cr = crLookups.get(url); + if (cr != null) { + if (cr.error != null) { + warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, url, cr.error); + } else { + sd = (StructureDefinition) cr.resource; + } + } else { + try { + sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, url); + crLookups.put(url, new CanonicalResourceLookupResult(sd)); + } catch (Exception e) { + if (STACK_TRACE) { e.printStackTrace(); } + crLookups.put(url, new CanonicalResourceLookupResult(e.getMessage())); + warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage()); + } + if (sd != null) { + context.cacheResource(sd); + } + } + return sd; + } + // private void plog(String msg) { // long n = System.currentTimeMillis(); // String elapsed = Utilities.padLeft(Long.toString(n-start), ' ', 5); @@ -5720,7 +5764,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat boolean ok; try { long t = System.nanoTime(); - ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); + ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); timeTracker.fpe(t); msg = fpe.forLog(); } catch (Exception ex) { 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 00b6cca24..24ec6b3f1 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 @@ -419,7 +419,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe private void checkOutcomes(List errors, JsonObject focus, String profile, String name) throws IOException { JsonObject java = focus.getAsJsonObject("java"); - OperationOutcome goal = (OperationOutcome) new JsonParser().parse(java.getAsJsonObject("outcome")); + OperationOutcome goal = java.has("outcome") ? (OperationOutcome) new JsonParser().parse(java.getAsJsonObject("outcome")) : new OperationOutcome(); OperationOutcome actual = OperationOutcomeUtilities.createOutcomeSimple(errors); actual.setText(null); String json = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(actual); @@ -521,8 +521,10 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe if (java.has("outcome")) { java.remove("outcome"); } - JsonObject oj = JsonTrackingParser.parse(json, null); - java.add("outcome", oj); + if (actual.hasIssue()) { + JsonObject oj = JsonTrackingParser.parse(json, null); + java.add("outcome", oj); + } } } From ac3d4add7e9850091cbf36128adc6aa814d620be Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 5 Oct 2022 21:19:52 +1100 Subject: [PATCH 5/6] fix error in cnl-5 in R5 ballot --- .../validation/instance/utils/FHIRPathExpressionFixer.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java index be2590646..979ba0e16 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java @@ -45,6 +45,13 @@ public class FHIRPathExpressionFixer { return ("name.exists() implies name.matches('[A-Z]([A-Za-z0-9_]){0,254}')"); } + // R5 ballot + if (expr.equals("url.matches('([^|#])*')")) { + return ("$this.matches('([^|#])*')"); + } + + + // clarification in FHIRPath spec if ("eld-19".equals(key)) { From c45a61a0be4492884c55d123b1d92dd994fa8431 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 5 Oct 2022 21:20:04 +1100 Subject: [PATCH 6/6] Add test case for xml parser problem --- .../org/hl7/fhir/r5/test/GeneralTests.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java new file mode 100644 index 000000000..a4dbf4683 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java @@ -0,0 +1,36 @@ +package org.hl7.fhir.r5.test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.junit.jupiter.api.Test; + + +public class GeneralTests { + + @Test + void testXMLParse() throws IOException { + System.out.println(System.getProperty("java.vm.name")); + InputStream stream = TestingUtilities.loadTestResourceStream("validator", + "xml_v10.xml"); + org.hl7.fhir.r5.elementmodel.XmlParser xp = new org.hl7.fhir.r5.elementmodel.XmlParser(TestingUtilities.getSharedWorkerContext()); + xp.setAllowXsiLocation(true); + List errorList = new ArrayList<>(); + xp.setupValidation(ValidationPolicy.EVERYTHING, errorList); + try { + Object resource = xp.parse(stream); + } catch (Exception e) { + e.printStackTrace(); + } + for (ValidationMessage message : errorList) { + System.out.println(message.getMessage()); + } + } + +}