From 808d4619d6e6f2fe33e55ed231c0928c1a6a7910 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 3 Apr 2024 20:14:27 +1100 Subject: [PATCH] various improvements to structure map validation --- .../instance/InstanceValidator.java | 5 +- .../instance/type/StructureMapValidator.java | 56 ++++++++++++++----- 2 files changed, 45 insertions(+), 16 deletions(-) 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 8c9041bbb..d4ecb2c94 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 @@ -195,6 +195,7 @@ import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities.DecimalStatus; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo; +import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.validation.IDigitalSignatureServices; @@ -3810,7 +3811,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat size = cnt.length; } } else if (url.startsWith("file:")) { - size = new File(url.substring(5)).length(); + size = ManagedFileAccess.file(url.substring(5)).length(); } else { fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url); } } catch (Exception e) { @@ -5699,7 +5700,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (element.getType().equals("StructureDefinition")) { return new StructureDefinitionValidator(this, fpe, wantCheckSnapshotUnchanged).validateStructureDefinition(errors, element, stack) && ok; } else if (element.getType().equals("StructureMap")) { - return new StructureMapValidator(this, fpe, profileUtilities).validateStructureMap(errors, element, stack) && ok; + return new StructureMapValidator(this, fpe, profileUtilities).validateStructureMap(valContext, errors, element, stack) && ok; } else if (element.getType().equals("ValueSet")) { return new ValueSetValidator(this).validateValueSet(valContext, errors, element, stack) && ok; } else if ("http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/ViewDefinition".equals(element.getProperty().getStructure().getUrl())) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java index f535ad4e7..9e02eba9c 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java @@ -1,5 +1,6 @@ package org.hl7.fhir.validation.instance.type; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -30,6 +31,7 @@ import org.hl7.fhir.r5.terminologies.ValueSetUtilities; import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; import org.hl7.fhir.r5.utils.structuremap.ResolvedGroup; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; +import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; @@ -38,6 +40,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; import org.hl7.fhir.validation.BaseValidator; import org.hl7.fhir.validation.instance.utils.NodeStack; +import org.hl7.fhir.validation.instance.utils.ValidationContext; public class StructureMapValidator extends BaseValidator { @@ -198,6 +201,20 @@ public class StructureMapValidator extends BaseValidator { return summary(); } + public String getVersion() { + return VersionUtilities.getNameForVersion(sd.getFhirVersion().toCode()); + } + + public String sdSummary() { + if (sd == null) { + return "null"; + } else if (!sd.hasUrl()) { + return "??"; + } else { + return sd.getVersionedUrl(); + } + } + } public class VariableSet { @@ -313,7 +330,7 @@ public class StructureMapValidator extends BaseValidator { } - public boolean validateStructureMap(List errors, Element src, NodeStack stack) { + public boolean validateStructureMap(ValidationContext valContext, List errors, Element src, NodeStack stack) { boolean ok = true; List imports = src.getChildrenByName("import"); int cc = 0; @@ -334,7 +351,7 @@ public class StructureMapValidator extends BaseValidator { if (hasInputTypes(group) || group.hasUserData("structuremap.parameters")) { group.setUserData("structuremap.validated", true); fired = true; - ok = validateGroup(errors, src, group, stack.push(group, cc, null, null), grpNames) && ok; + ok = validateGroup(valContext, errors, src, group, stack.push(group, cc, null, null), grpNames) && ok; } } cc++; @@ -345,7 +362,7 @@ public class StructureMapValidator extends BaseValidator { for (Element group : groups) { if (!group.hasUserData("structuremap.validated")) { hint(errors, "2023-03-01", IssueType.INFORMATIONAL, group.line(), group.col(), stack.push(group, cc, null, null).getLiteralPath(), ok, I18nConstants.SM_ORPHAN_GROUP, group.getChildValue("name")); - ok = validateGroup(errors, src, group, stack.push(group, cc, null, null), grpNames) && ok; + ok = validateGroup(valContext, errors, src, group, stack.push(group, cc, null, null), grpNames) && ok; } cc++; } @@ -378,7 +395,7 @@ public class StructureMapValidator extends BaseValidator { return true; } - private boolean validateGroup(List errors, Element src, Element group, NodeStack stack, List grpNames) { + private boolean validateGroup(ValidationContext valContext, List errors, Element src, Element group, NodeStack stack, List grpNames) { String name = group.getChildValue("name"); boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name); if (!rule(errors, "2023-03-01", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), !grpNames.contains(name), I18nConstants.SM_GROUP_NAME_DUPLICATE, name)) { @@ -403,7 +420,7 @@ public class StructureMapValidator extends BaseValidator { List structures = src.getChildrenByName("structure"); int cc = 0; for (Element input : inputs) { - ok = validateInput(errors, src, group, input, stack.push(input, cc, null, null), structures, variables, pvars) && ok; + ok = validateInput(valContext, errors, src, group, input, stack.push(input, cc, null, null), structures, variables, pvars) && ok; cc++; } @@ -457,7 +474,7 @@ public class StructureMapValidator extends BaseValidator { return grp; } - private boolean validateInput(List errors, Element src, Element group, Element input, NodeStack stack, List structures, VariableSet variables, VariableSet pvars) { + private boolean validateInput(ValidationContext valContext, List errors, Element src, Element group, Element input, NodeStack stack, List structures, VariableSet variables, VariableSet pvars) { boolean ok = false; String gname = group.getChildValue("name"); String name = input.getChildValue("name"); @@ -493,8 +510,15 @@ public class StructureMapValidator extends BaseValidator { smode = structure.getChildValue("mode"); String url = structure.getChildValue("url"); sd = context.fetchResource(StructureDefinition.class, url); + if (sd == null) { + try { + sd = (StructureDefinition) fetcher.fetchCanonicalResource((IResourceValidator) parent, valContext.getAppContext(), url); + } catch (Exception e) { + // nothing? + } + } if (sd == null) { - rule(errors, "2023-03-01", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), sd != null, I18nConstants.SM_GROUP_INPUT_TYPE_UNKNOWN_STRUCTURE, type, url); + warning(errors, "2023-03-01", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), sd != null, I18nConstants.SM_GROUP_INPUT_TYPE_UNKNOWN_STRUCTURE, type, url); } } else if (type != null) { sd = context.fetchTypeDefinition(type); @@ -895,7 +919,7 @@ public class StructureMapValidator extends BaseValidator { if (srcED != null) { if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_SOURCE)) { srcVS = context.findTxResource(ValueSet.class, srcED.getBinding().getValueSet()); - if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcVS != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_SOURCE)) { + if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcVS != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_SOURCE, srcED.getBinding().getValueSet())) { ValueSetExpansionOutcome vse = context.expandVS(srcVS, true, false); if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE, vse.getError())) { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); @@ -914,7 +938,7 @@ public class StructureMapValidator extends BaseValidator { if (srcED != null) { if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, tgtED.getBinding().hasValueSet() && tgtED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_TARGET)) { ValueSet vs = context.findTxResource(ValueSet.class, tgtED.getBinding().getValueSet()); - if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vs != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_TARGET)) { + if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vs != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_TARGET, tgtED.getBinding().getValueSet())) { ValueSetExpansionOutcome vse = context.expandVS(vs, true, false); if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_TARGET, vse.getError())) { List systems = new ArrayList<>(); @@ -1065,6 +1089,9 @@ public class StructureMapValidator extends BaseValidator { throw new Error("Unable to resolve "+url); } else { ElementDefinition t2 = sdt.getSnapshot().getElementByPath(path); + if (t2 == null) { + t2 = sdt.getSnapshot().getElementById(path); + } if (t2 == null) { throw new Error("Unable to resolve "+path+" in "+url); } else { @@ -1128,8 +1155,9 @@ public class StructureMapValidator extends BaseValidator { String tgtType = tgtVar.getWorkingType(); if (rule(errors, "2023-03-01", IssueType.NOTFOUND, dependent.line(), dependent.col(), stack.getLiteralPath(), srcType != null, I18nConstants.SM_SOURCE_TYPE_NOT_FOUND) && rule(errors, "2023-03-01", IssueType.NOTFOUND, dependent.line(), dependent.col(), stack.getLiteralPath(), tgtType != null, I18nConstants.SM_TARGET_TYPE_NOT_FOUND)) { - StructureMapGroupComponent grp = findDefaultGroup(src, srcType, tgtType); - ok = rule(errors, "2023-03-01", IssueType.NOTFOUND, dependent.line(), dependent.col(), stack.getLiteralPath(), grp != null, I18nConstants.SM_MATCHING_RULEGROUP_NOT_FOUND, srcType, tgtType) && ok; + StructureMapGroupComponent grp = findDefaultGroup(src, srcType, srcVar.sd.getUrl(), tgtType, tgtVar.sd.getUrl()); + ok = rule(errors, "2023-03-01", IssueType.NOTFOUND, dependent.line(), dependent.col(), stack.getLiteralPath(), grp != null, I18nConstants.SM_MATCHING_RULEGROUP_NOT_FOUND, + srcType+" ("+srcVar.sdSummary()+")", tgtType+" ("+tgtVar.sdSummary()+")") && ok; } else { ok = false; } @@ -1213,7 +1241,7 @@ public class StructureMapValidator extends BaseValidator { return type; } - private StructureMapGroupComponent findDefaultGroup(Element src, String srcType, String tgtType) { + private StructureMapGroupComponent findDefaultGroup(Element src, String srcType, String srcUrl, String tgtType, String tgtUrl) { List groups = src.getChildrenByName("group"); for (Element group : groups) { if (Utilities.existsInList(group.getChildValue("typeMode"), "types", "type-and-types")) { @@ -1221,7 +1249,7 @@ public class StructureMapValidator extends BaseValidator { if (inputs.size() == 2 && "source".equals(inputs.get(0).getChildValue("mode")) && "source".equals(inputs.get(0).getChildValue("mode"))) { String srcT = resolveInputType(src, inputs.get(0)); String tgtT = resolveInputType(src, inputs.get(1)); - if (sameTypes(srcT, srcType) && sameTypes(tgtT, tgtType)) { + if (sameTypes(srcT, srcType) && sameTypes(tgtT, tgtType) || sameTypes(srcT, srcUrl) && sameTypes(tgtT, tgtUrl)) { return makeGroupComponent(group); } } @@ -1233,7 +1261,7 @@ public class StructureMapValidator extends BaseValidator { grp.getInput().size() == 2 && grp.getInput().get(0).getMode() == StructureMapInputMode.SOURCE && grp.getInput().get(1).getMode() == StructureMapInputMode.TARGET) { String srcT = resolveInputType(map, grp.getInput().get(0)); String tgtT = resolveInputType(map, grp.getInput().get(1)); - if (sameTypes(srcT, srcType) && sameTypes(tgtT, tgtType)) { + if (sameTypes(srcT, srcType) && sameTypes(tgtT, tgtType) || sameTypes(srcT, srcUrl) && sameTypes(tgtT, tgtUrl)) { return grp; } }