Merge pull request #1801 from hapifhir/2024-11-gg-slice-validation

2024 11 gg slice validation
This commit is contained in:
Grahame Grieve 2024-11-09 18:01:00 +10:30 committed by GitHub
commit 1a2c25b531
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 57 additions and 24 deletions

View File

@ -1,14 +1,16 @@
## Validator Changes ## 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 * Fix issue where valdiator not retaining extension context when checking constraint expressions in profiles
* Validate min-length when found in extension * Validate min-length when found in extension
* Correct bug parsing json-property-key values with meant validation failed * Correct bug parsing json-property-key values with meant validation failed
* Fix problem validating json-property-key value pairs * Fix problem validating json-property-key value pairs
* Fix special case r5 loading of terminology to fix validation error on ExampleScenario * Fix special case r5 loading of terminology to fix validation error on ExampleScenario
* Improve handling of JSON format errors * 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 * Fix slicing by type and profile to allow multiple options per slice
* List measure choices when a match by version can't be found * List measure choices when a match by version can't be found
* Validate fhirpath expression in slice discriminators
## Other code changes ## Other code changes

View File

@ -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.SchematronType;
import org.hl7.fhir.utilities.xml.SchematronWriter.Section; import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
/** /**
* This class provides a set of utility operations for working with Profiles. * This class provides a set of utility operations for working with Profiles.
* Key functionality: * Key functionality:
@ -490,8 +491,8 @@ public class ProfileUtilities {
return this; return this;
} }
public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException { public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes) throws DefinitionException {
String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath()); String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath())+"."+chaseTypes;
if (childMapCache.containsKey(cacheKey)) { if (childMapCache.containsKey(cacheKey)) {
return childMapCache.get(cacheKey); return childMapCache.get(cacheKey);
} }
@ -519,7 +520,7 @@ public class ProfileUtilities {
for (ElementDefinition e : list) { for (ElementDefinition e : list) {
if (id.equals(e.getId())) 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())); 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 } else
break; 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); SourcedChildDefinitions result = new SourcedChildDefinitions(src, res);
childMapCache.put(cacheKey, result); childMapCache.put(cacheKey, result);
return result; return result;
@ -4085,7 +4110,7 @@ public class ProfileUtilities {
private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException { private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
ElementDefinition ed = profile.getSnapshot().getElementFirstRep(); 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)); 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()) { for (ElementDefinition child : children.getList()) {
if (child.getPath().endsWith(".id")) { 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)); 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 { } else {
org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile)); 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; boolean hasValue = false;
SourcedChildDefinitions children = getChildMap(profile, ed); SourcedChildDefinitions children = getChildMap(profile, ed, true);
for (ElementDefinition child : children.getList()) { for (ElementDefinition child : children.getList()) {
if (!child.hasContentReference()) { if (!child.hasContentReference()) {
org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor); org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);

View File

@ -93,7 +93,7 @@ public class ObjectConverter {
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
res.setValue(((PrimitiveType) base).asStringValue()); 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()) { for (ElementDefinition child : children.getList()) {
String n = tail(child.getPath()); String n = tail(child.getPath());
if (sd.getKind() != StructureDefinitionKind.PRIMITIVETYPE || !"value".equals(n)) { if (sd.getKind() != StructureDefinitionKind.PRIMITIVETYPE || !"value".equals(n)) {

View File

@ -385,7 +385,7 @@ public class Property {
ElementDefinition ed = definition; ElementDefinition ed = definition;
StructureDefinition sd = structure; StructureDefinition sd = structure;
boolean isCDA = isCDAElement(structure); boolean isCDA = isCDAElement(structure);
SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed); SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false);
String url = null; String url = null;
if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) { if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) {
// ok, find the right definitions // ok, find the right definitions
@ -459,7 +459,7 @@ public class Property {
sd = context.fetchResource(StructureDefinition.class, url); sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) if (sd == null)
throw new DefinitionException("Unable to find definition '"+url+"' for type '"+t+"' for name '"+elementName+"' on property "+definition.getPath()); 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<Property> properties = new ArrayList<Property>(); List<Property> properties = new ArrayList<Property>();
@ -493,7 +493,7 @@ public class Property {
protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException { protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
ElementDefinition ed = definition; ElementDefinition ed = definition;
StructureDefinition sd = structure; StructureDefinition sd = structure;
SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed); SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed, false);
if (children.getList().isEmpty()) { if (children.getList().isEmpty()) {
// ok, find the right definitions // ok, find the right definitions
String t = null; String t = null;
@ -519,7 +519,7 @@ public class Property {
sd = context.fetchResource(StructureDefinition.class, t); sd = context.fetchResource(StructureDefinition.class, t);
if (sd == null) if (sd == null)
throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath()); 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<Property> properties = new ArrayList<Property>(); List<Property> properties = new ArrayList<Property>();

View File

@ -6619,7 +6619,7 @@ public class FHIRPathEngine {
focus = element; focus = element;
} else { } else {
SourcedChildDefinitions childDefinitions; 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 that's empty, get the children of the type
if (childDefinitions.getList().isEmpty()) { if (childDefinitions.getList().isEmpty()) {
@ -6627,7 +6627,7 @@ public class FHIRPathEngine {
if (sd == null) { if (sd == null) {
throw makeException(expr, I18nConstants.FHIRPATH_RESOLVE_DISCRIMINATOR_CANT_FIND, element.getElement().getType().get(0).getProfile(), element.getElement().getId()); 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()) { 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) 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()); focus = new TypedElementDefinition(sd.getSnapshot().getElementFirstRep());
} else if ("extension".equals(expr.getName())) { } else if ("extension".equals(expr.getName())) {
String targetUrl = expr.getParameters().get(0).getConstant().primitiveValue(); 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()) { for (ElementDefinition t : childDefinitions.getList()) {
if (t.getPath().endsWith(".extension") && t.hasSliceName()) { if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ? 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); exsd = worker.fetchResource(StructureDefinition.class, exsd.getBaseDefinition(), exsd);
} }
if (exsd != null && exsd.getUrl().equals(targetUrl)) { if (exsd != null && exsd.getUrl().equals(targetUrl)) {
if (profileUtilities.getChildMap(sd, t).getList().isEmpty()) { if (profileUtilities.getChildMap(sd, t, false).getList().isEmpty()) {
sd = exsd; sd = exsd;
} }
focus = new TypedElementDefinition(t); focus = new TypedElementDefinition(t);

View File

@ -99,7 +99,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
if (sd == null) if (sd == null)
return "unknown resource " +res.fhirType(); return "unknown resource " +res.fhirType();
else { else {
SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed); SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed, true);
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; "); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; ");
for (NamedResourceWrapperList p : res.childrenInGroups()) { for (NamedResourceWrapperList p : res.childrenInGroups()) {
ElementDefinition pDefn = getElementDefinition(childDefs, p); ElementDefinition pDefn = getElementDefinition(childDefs, p);

View File

@ -2700,7 +2700,7 @@ public class StructureMapUtilities {
private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException {
boolean first = true; boolean first = true;
List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed).getList(); List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed, true).getList();
for (ElementDefinition child : children) { for (ElementDefinition child : children) {
if (first && inner) { if (first && inner) {
b.append(" then {\r\n"); b.append(" then {\r\n");

View File

@ -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_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_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_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_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_MISMATCH = "SD_EXTENSION_URL_MISMATCH";
public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING"; public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING";

View File

@ -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_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_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_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_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_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} SD_EXTENSION_URL_MISSING = The value of Extension.url is not fixed to the extension URL {0}

View File

@ -6368,7 +6368,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
// get the list of direct defined children, including slices // 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 (childDefinitions.getList().isEmpty()) {
if (actualType == null) { if (actualType == null) {
vi.setValid(false); 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)); throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType));
trackUsage(dt, valContext, element); 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; return childDefinitions;
} }
@ -6998,7 +6998,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (ed.hasFixedCoding() && "http://loinc.org".equals(ed.getFixedCoding().getSystem())) { if (ed.hasFixedCoding() && "http://loinc.org".equals(ed.getFixedCoding().getSystem())) {
return ed.getFixedCoding().getCode(); return ed.getFixedCoding().getCode();
} }
SourcedChildDefinitions children = profileUtilities.getChildMap(profile, ed); SourcedChildDefinitions children = profileUtilities.getChildMap(profile, ed, true);
if (children != null) { if (children != null) {
for (ElementDefinition t : children.getList()) { for (ElementDefinition t : children.getList()) {
if (t.getPath().endsWith(".code") && t.hasFixed()) { if (t.getPath().endsWith(".code") && t.hasFixed()) {

View File

@ -504,11 +504,15 @@ public class StructureDefinitionValidator extends BaseValidator {
Element pattern = element.getNamedChild("pattern"); Element pattern = element.getNamedChild("pattern");
if (pattern != null) { if (pattern != null) {
NodeStack fn = stack.push(pattern, 0, null, null); 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())) { 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; 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 { } else {
ok = false; ok = false;
} }
} else {
ok = false;
}
} }
} }
} }