diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7ab9bcb1d..98dc13da7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,14 +1,16 @@ ## Validator Changes +* Support authentication for terminology servers (see https://confluence.hl7.org/display/FHIR/Using+fhir-settings.json) * Fix issue where valdiator not retaining extension context when checking constraint expressions in profiles * Validate min-length when found in extension * Correct bug parsing json-property-key values with meant validation failed * Fix problem validating json-property-key value pairs * Fix special case r5 loading of terminology to fix validation error on ExampleScenario * Improve handling of JSON format errors +* Fix bug where extension slices defined in other profiles are not found when processing slices based on extension +* Validate fhirpath expression in slice discriminators * Fix slicing by type and profile to allow multiple options per slice * List measure choices when a match by version can't be found -* Validate fhirpath expression in slice discriminators ## Other code changes 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 46bbab983..4aa657d35 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 @@ -119,6 +119,7 @@ import org.hl7.fhir.utilities.xml.SchematronWriter.Rule; import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType; import org.hl7.fhir.utilities.xml.SchematronWriter.Section; + /** * This class provides a set of utility operations for working with Profiles. * Key functionality: @@ -490,8 +491,8 @@ public class ProfileUtilities { return this; } - public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException { - String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath()); + public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes) throws DefinitionException { + String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath())+"."+chaseTypes; if (childMapCache.containsKey(cacheKey)) { return childMapCache.get(cacheKey); } @@ -519,7 +520,7 @@ public class ProfileUtilities { for (ElementDefinition e : list) { if (id.equals(e.getId())) - return getChildMap(src, e); + return getChildMap(src, e, true); } throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath())); @@ -536,6 +537,30 @@ public class ProfileUtilities { } else break; } + if (res.isEmpty() && chaseTypes) { + // we've got no in-line children. Some consumers of this routine will figure this out for themselves but most just want to walk into + // the type children. + src = null; + if (element.getType().isEmpty()) { + throw new DefinitionException("No defined children and no type information on element '"+element.getId()+"'"); + } else if (element.getType().size() > 1) { + throw new DefinitionException("No defined children and multiple possible types '"+element.typeSummary()+"' on element '"+element.getId()+"'"); + } else if (element.getType().get(0).getProfile().size() > 1) { + throw new DefinitionException("No defined children and multiple possible type profiles '"+element.typeSummary()+"' on element '"+element.getId()+"'"); + } else if (element.getType().get(0).hasProfile()) { + src = context.fetchResource(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue()); + if (src == null) { + throw new DefinitionException("No defined children and unknown type profile '"+element.typeSummary()+"' on element '"+element.getId()+"'"); + } + } else { + src = context.fetchTypeDefinition(element.getType().get(0).getWorkingCode()); + if (src == null) { + throw new DefinitionException("No defined children and unknown type '"+element.typeSummary()+"' on element '"+element.getId()+"'"); + } + } + SourcedChildDefinitions scd = getChildMap(src, src.getSnapshot().getElementFirstRep(), false); + res = scd.list; + } SourcedChildDefinitions result = new SourcedChildDefinitions(src, res); childMapCache.put(cacheKey, result); return result; @@ -4085,7 +4110,7 @@ public class ProfileUtilities { private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile)); - SourcedChildDefinitions children = getChildMap(profile, ed); + SourcedChildDefinitions children = getChildMap(profile, ed, true); for (ElementDefinition child : children.getList()) { if (child.getPath().endsWith(".id")) { org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile)); @@ -4107,7 +4132,7 @@ public class ProfileUtilities { } else { org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); boolean hasValue = false; - SourcedChildDefinitions children = getChildMap(profile, ed); + SourcedChildDefinitions children = getChildMap(profile, ed, true); for (ElementDefinition child : children.getList()) { if (!child.hasContentReference()) { org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java index 6c9c26591..6d366baaa 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java @@ -93,7 +93,7 @@ public class ObjectConverter { if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) res.setValue(((PrimitiveType) base).asStringValue()); - SourcedChildDefinitions children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); + SourcedChildDefinitions children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep(), true); for (ElementDefinition child : children.getList()) { String n = tail(child.getPath()); if (sd.getKind() != StructureDefinitionKind.PRIMITIVETYPE || !"value".equals(n)) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java index ec7cf72d0..7f5e814a3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java @@ -385,7 +385,7 @@ public class Property { ElementDefinition ed = definition; StructureDefinition sd = structure; boolean isCDA = isCDAElement(structure); - SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed); + SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false); String url = null; if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) { // ok, find the right definitions @@ -459,7 +459,7 @@ public class Property { sd = context.fetchResource(StructureDefinition.class, url); if (sd == null) throw new DefinitionException("Unable to find definition '"+url+"' for type '"+t+"' for name '"+elementName+"' on property "+definition.getPath()); - children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); + children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0), false); } } List properties = new ArrayList(); @@ -493,7 +493,7 @@ public class Property { protected List getChildProperties(TypeDetails type) throws DefinitionException { ElementDefinition ed = definition; StructureDefinition sd = structure; - SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed); + SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false); if (children.getList().isEmpty()) { // ok, find the right definitions String t = null; @@ -519,7 +519,7 @@ public class Property { sd = context.fetchResource(StructureDefinition.class, t); if (sd == null) throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath()); - children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); + children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0), false); } } List properties = new ArrayList(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java index 3db26b7e7..c89561a64 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/fhirpath/FHIRPathEngine.java @@ -6619,7 +6619,7 @@ public class FHIRPathEngine { focus = element; } else { SourcedChildDefinitions childDefinitions; - childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); + childDefinitions = profileUtilities.getChildMap(sd, element.getElement(), false); // if that's empty, get the children of the type if (childDefinitions.getList().isEmpty()) { @@ -6627,7 +6627,7 @@ public class FHIRPathEngine { if (sd == null) { throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId()); } - childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep()); + childDefinitions = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep(), false); } for (ElementDefinition t : childDefinitions.getList()) { if (tailMatches(t, expr.getName()) && !t.hasSlicing()) { // GG: slicing is a problem here. This is for an exetnsion with a fixed value (type slicing) @@ -6657,7 +6657,7 @@ public class FHIRPathEngine { focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep()); } else if ("extension".equals(expr.getName())) { String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); - SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(sd, element.getElement()); + SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(sd, element.getElement(), true); for (ElementDefinition t : childDefinitions.getList()) { if (t.getPath().endsWith(".extension") && t.hasSliceName()) { StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ? @@ -6666,7 +6666,7 @@ public class FHIRPathEngine { exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd); } if (exsd != null && exsd.getUrl().equals(targetUrl)) { - if (profileUtilities.getChildMap(sd, t).getList().isEmpty()) { + if (profileUtilities.getChildMap(sd, t, false).getList().isEmpty()) { sd = exsd; } focus = new TypedElementDefinition(t); 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 90b3d55ac..aabf040d2 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 @@ -99,7 +99,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (sd == null) return "unknown resource " +res.fhirType(); else { - SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed); + SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed, true); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; "); for (NamedResourceWrapperList p : res.childrenInGroups()) { ElementDefinition pDefn = getElementDefinition(childDefs, p); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java index 0cabfd16b..f22539dd0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -2700,7 +2700,7 @@ public class StructureMapUtilities { private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { boolean first = true; - List children = profileUtilities.getChildMap(sd, ed).getList(); + List children = profileUtilities.getChildMap(sd, ed, true).getList(); for (ElementDefinition child : children) { if (first && inner) { b.append(" then {\r\n"); 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 e5c029607..c36b67897 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 @@ -589,6 +589,7 @@ public class I18nConstants { public static final String SD_ELEMENT_FIXED_WRONG_TYPE = "SD_ELEMENT_FIXED_WRONG_TYPE"; public static final String SD_ELEMENT_NOT_IN_CONSTRAINT = "SD_ELEMENT_NOT_IN_CONSTRAINT"; public static final String SD_ELEMENT_PATTERN_WRONG_TYPE = "SD_ELEMENT_PATTERN_WRONG_TYPE"; + public static final String SD_ELEMENT_PATTERN_NO_FIXED = "SD_ELEMENT_PATTERN_NO_FIXED"; public static final String SD_ELEMENT_REASON_DERIVED = "SD_ELEMENT_REASON_DERIVED"; public static final String SD_EXTENSION_URL_MISMATCH = "SD_EXTENSION_URL_MISMATCH"; public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index b4c87aeaa..2e63e64e5 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -593,7 +593,8 @@ SD_ED_TYPE_PROFILE_WRONG_TYPE_one = The type {0} is not in the list of allowed t SD_ED_TYPE_PROFILE_WRONG_TYPE_other = The type {0} is not in the list of allowed types {1} in the profile {2} SD_ELEMENT_FIXED_WRONG_TYPE = The base element has a fixed type of ''{0}'', so this element must have a fixed value of the same type, not ''{1}'' SD_ELEMENT_NOT_IN_CONSTRAINT = The element definition for {1} has a property {0} which is not allowed in a profile -SD_ELEMENT_PATTERN_WRONG_TYPE = The base element has a pattern type of ''{0}'', so this element must have a pattern value of the same type, not ''{1}'' +SD_ELEMENT_PATTERN_WRONG_TYPE = The base element has a pattern type of ''{0}'', so this element must have a fixed value of the same type, not ''{1}'' +SD_ELEMENT_PATTERN_NO_FIXED = The base element has a pattern type of ''{0}'', so this element must have a fixed value but it doesn''t SD_ELEMENT_REASON_DERIVED = , because the value must match the fixed value define in ''{0}'' SD_EXTENSION_URL_MISMATCH = The fixed value for the extension URL is {1} which doesn''t match the canonical URL {0} SD_EXTENSION_URL_MISSING = The value of Extension.url is not fixed to the extension URL {0} 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 c598bae08..e8d0912d7 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 @@ -6368,7 +6368,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } // get the list of direct defined children, including slices - SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(profile, definition); + SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(profile, definition, false); if (childDefinitions.getList().isEmpty()) { if (actualType == null) { vi.setValid(false); @@ -6457,7 +6457,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType)); trackUsage(dt, valContext, element); - childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0)); + childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0), false); return childDefinitions; } @@ -6998,7 +6998,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (ed.hasFixedCoding() && "http://loinc.org".equals(ed.getFixedCoding().getSystem())) { return ed.getFixedCoding().getCode(); } - SourcedChildDefinitions children = profileUtilities.getChildMap(profile, ed); + SourcedChildDefinitions children = profileUtilities.getChildMap(profile, ed, true); if (children != null) { for (ElementDefinition t : children.getList()) { if (t.getPath().endsWith(".code") && t.hasFixed()) { 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 1acff417c..235a448a8 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 @@ -503,9 +503,13 @@ public class StructureDefinitionValidator extends BaseValidator { } else { Element pattern = element.getNamedChild("pattern"); if (pattern != null) { - NodeStack fn = stack.push(pattern, 0, null, null); - if (rule(errors, "2024-03-26", IssueType.INVALID, fn, pattern.fhirType().equals(ed.getFixed().fhirType()), I18nConstants.SD_ELEMENT_PATTERN_WRONG_TYPE, pattern.fhirType(), ed.getFixed().fhirType())) { - ok = ((org.hl7.fhir.validation.instance.InstanceValidator) parent).checkFixedValue(errors, path, pattern, ed.getFixed(), base.getVersionedUrl(), "pattern", element, true, context.formatMessage(I18nConstants.SD_ELEMENT_REASON_DERIVED, base.getVersionedUrl())) && ok; + NodeStack fn = stack.push(pattern, 0, null, null); + if (rule(errors, "2024-03-26", IssueType.INVALID, fn, ed.hasFixed(), I18nConstants.SD_ELEMENT_PATTERN_NO_FIXED, pattern.fhirType())) { + if (rule(errors, "2024-03-26", IssueType.INVALID, fn, pattern.fhirType().equals(ed.getFixed().fhirType()), I18nConstants.SD_ELEMENT_PATTERN_WRONG_TYPE, pattern.fhirType(), ed.getFixed().fhirType())) { + ok = ((org.hl7.fhir.validation.instance.InstanceValidator) parent).checkFixedValue(errors, path, pattern, ed.getFixed(), base.getVersionedUrl(), "pattern", element, true, context.formatMessage(I18nConstants.SD_ELEMENT_REASON_DERIVED, base.getVersionedUrl())) && ok; + } else { + ok = false; + } } else { ok = false; }