From 035841f13eaf48528079f45fc8d4309ebe70d026 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 22 May 2023 21:07:17 +1000 Subject: [PATCH 01/10] #692 - error when meaning-when-missing found in profiles --- .../org/hl7/fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 2 +- .../type/StructureDefinitionValidator.java | 14 +++++++++----- 3 files changed, 11 insertions(+), 6 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 5b5dcf412..c2439259f 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 @@ -869,6 +869,7 @@ public class I18nConstants { public static final String ED_PATH_WRONG_TYPE_MATCH = "ED_PATH_WRONG_TYPE_MATCH"; public static final String ATTEMPT_TO_CHANGE_SLICING = "ATTEMPT_TO_CHANGE_SLICING"; public static final String REPEAT_SLICING_IGNORED = "REPEAT_SLICING_IGNORED"; + public static final String SD_ELEMENT_NOT_IN_CONSTRAINT = "SD_ELEMENT_NOT_IN_CONSTRAINT"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index c7d8d4fa5..9bd8f16e8 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -923,4 +923,4 @@ SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type: extensions should ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type list is not constrained ATTEMPT_TO_CHANGE_SLICING = The element at {0} defines the slicing {1} but then an element in the slicing {2} tries to redefine the slicing to {3} REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored - +SD_ELEMENT_NOT_IN_CONSTRAINT = The element definition for {1} has a property {0} which is not allowed in a profile 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 9cad656d9..9ed1ddc95 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 @@ -127,14 +127,16 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); ok = false; } + List differentials = src.getChildrenByName("differential"); List snapshots = src.getChildrenByName("snapshot"); boolean logical = "logical".equals(src.getNamedChildValue("kind")); + boolean constraint = "constraint".equals(src.getNamedChildValue("derivation")); for (Element differential : differentials) { - ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical) && ok; + ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint) && ok; } for (Element snapshot : snapshots) { - ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName, logical) && ok; + ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName, logical, constraint) && ok; } return ok; } @@ -174,18 +176,18 @@ public class StructureDefinitionValidator extends BaseValidator { } } - private boolean validateElementList(List errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical) { + private boolean validateElementList(List errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) { boolean ok = true; List elements = elementList.getChildrenByName("element"); int cc = 0; for (Element element : elements) { - ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical) && ok; + ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint) && ok; cc++; } return ok; } - private boolean validateElementDefinition(List errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical) { + private boolean validateElementDefinition(List errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) { boolean ok = true; boolean typeMustSupport = false; String path = element.getNamedChildValue("path"); @@ -194,6 +196,8 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path); } + rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), !constraint || !element.hasChild("meaningWhenMissing"), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path); + List types = element.getChildrenByName("type"); Set typeCodes = new HashSet<>(); Set characteristics = new HashSet<>(); From 49571de18ae3e9ab3d84fc0838612658112155d7 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 06:13:05 +1000 Subject: [PATCH 02/10] #1266 fix Bundle.getLink() for enumeration --- org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Bundle.java | 2 +- .../src/main/java/org/hl7/fhir/r5/utils/ResourceUtilities.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Bundle.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Bundle.java index f0deecff1..f0af48d82 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Bundle.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Bundle.java @@ -5307,7 +5307,7 @@ public class Bundle extends Resource implements IBaseBundle { public BundleLinkComponent getLink(String theRelation) { org.apache.commons.lang3.Validate.notBlank(theRelation, "theRelation may not be null or empty"); for (BundleLinkComponent next : getLink()) { - if (theRelation.equals(next.getRelation())) { + if (theRelation.equals(next.getRelation().toCode())) { return next; } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceUtilities.java index 78b381156..030ee34a9 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceUtilities.java @@ -127,7 +127,7 @@ public class ResourceUtilities { public static String getLink(Bundle feed, String rel) { for (BundleLinkComponent link : feed.getLink()) { - if (link.getRelation().equals(rel)) + if (link.getRelation().toCode().equals(rel)) return link.getUrl(); } return null; From 8a5c9c7ca65ff74bea4ad6a28bdd92ed0b289a59 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 17:37:11 +1000 Subject: [PATCH 03/10] Adjust handling of slicing min issues --- .../profile/ProfilePathProcessor.java | 8 +--- .../conformance/profile/ProfileUtilities.java | 45 ++++++++++++------- 2 files changed, 30 insertions(+), 23 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 dfe5d9f8f..ea546a25b 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 @@ -174,17 +174,13 @@ public class ProfilePathProcessor { // in the simple case, source is not sliced. if (!currentBase.hasSlicing() || currentBasePath.equals(getSlicing().getPath())) { - ElementDefinition currentRes = processSimplePath(currentBase, currentBasePath, diffMatches, typeList, - cursors - ); + ElementDefinition currentRes = processSimplePath(currentBase, currentBasePath, diffMatches, typeList, cursors); if (res == null) { res = currentRes; } } else { - processPathWithSlicedBase(currentBase, currentBasePath, diffMatches, typeList, - cursors - ); + processPathWithSlicedBase(currentBase, currentBasePath, diffMatches, typeList, cursors); } } 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 2279c688b..51585d403 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 @@ -135,6 +135,7 @@ public class ProfileUtilities extends TranslatingUtilities { public class ElementDefinitionCounter { int count = 0; ElementDefinition focus; + Set names = new HashSet<>(); public ElementDefinitionCounter(ElementDefinition ed) { focus = ed; @@ -142,15 +143,16 @@ public class ProfileUtilities extends TranslatingUtilities { public int update() { if (count > focus.getMin()) { - int was = focus.getMin(); - focus.setMin(count); - return was; + return count; } return -1; } - public void count(ElementDefinition ed) { - count = count + ed.getMin(); + public boolean count(ElementDefinition ed, String name) { + count = count + ed.getMin(); + boolean ok = !names.contains(name); + names.add(name); + return ok; } public ElementDefinition getFocus() { @@ -725,13 +727,13 @@ public class ProfileUtilities extends TranslatingUtilities { if (tn.contains("/")) { tn = tn.substring(tn.lastIndexOf("/")+1); } - System.out.println("Check slicing for "+derived.getVersionedUrl()); +// System.out.println("Check slicing for "+derived.getVersionedUrl()); Map slices = new HashMap<>(); int i = 0; for (ElementDefinition ed : derived.getSnapshot().getElement()) { if (ed.hasSlicing()) { slices.put(ed.getPath(), new ElementDefinitionCounter(ed)); - System.out.println("Entering slicing for "+ed.getPath()+" ["+i+"]"); +// System.out.println("Entering slicing for "+ed.getPath()+" ["+i+"]"); } else { Set toRemove = new HashSet<>(); for (String s : slices.keySet()) { @@ -740,13 +742,14 @@ public class ProfileUtilities extends TranslatingUtilities { } } for (String s : toRemove) { - int was = slices.get(s).update(); - if (was > -1) { - String msg = "The slice definition for "+slices.get(s).getFocus().getId()+" had a minimum of "+was+" but the slices added up to a minimum of "+slices.get(s).getFocus().getMin()+" so the value has been adjusted in the snapshot"; - System.out.println(msg); + int count = slices.get(s).update(); + if (count > -1) { + String msg = "The slice definition for "+slices.get(s).getFocus().getId()+" has a minimum of "+slices.get(s).getFocus().getMin()+" but the slices add up to a minimum of "+count; + //+" so the value has been adjusted in the snapshot"; we don't adjust it because of downstream effects. But if it's for publication, they better get it right. +// System.out.println(msg); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION)); } - System.out.println("Exiting slicing for "+s+" at "+ed.getPath()+" ["+i+"]"); +// System.out.println("Exiting slicing for "+s+" at "+ed.getPath()+" ["+i+"]"); slices.remove(s); } } @@ -758,7 +761,10 @@ public class ProfileUtilities extends TranslatingUtilities { messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); } if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { - slices.get(ed.getPath()).count(ed); + if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) { + String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])"; + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); + } } i++; } @@ -2043,10 +2049,15 @@ public class ProfileUtilities extends TranslatingUtilities { } // Before applying changes, apply them to what's in the profile StructureDefinition profile = null; - if (base.hasSliceName()) + if (base.hasSliceName()) { profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; - if (profile==null) + } + if (profile==null) { profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue(), derivedSrc) : null; + if (profile != null && !"Extension".equals(profile.getType())) { + profile = null; + } + } if (profile != null) { ElementDefinition e = profile.getSnapshot().getElement().get(0); String webroot = profile.getUserString("webroot"); @@ -2157,7 +2168,7 @@ public class ProfileUtilities extends TranslatingUtilities { if (derived.hasMinElement()) { if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR)); base.setMinElement(derived.getMinElement().copy()); } else if (trimDifferential) derived.setMinElement(null); @@ -2168,7 +2179,7 @@ public class ProfileUtilities extends TranslatingUtilities { if (derived.hasMaxElement()) { if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { if (isLargerMax(derived.getMax(), base.getMax())) - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); base.setMaxElement(derived.getMaxElement().copy()); } else if (trimDifferential) derived.setMaxElement(null); From a2e2ef714ed7024dcd788a855d20d949aeeab07c Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 17:39:08 +1000 Subject: [PATCH 04/10] Handle sub-slicing case where slice matches both the slice definition and the sub-slice definition --- .../fhir/r5/context/BaseWorkerContext.java | 17 +++++++++-- .../hl7/fhir/r5/context/ContextUtilities.java | 4 ++- .../hl7/fhir/r5/context/IWorkerContext.java | 2 ++ .../java/org/hl7/fhir/r5/model/Coding.java | 2 +- .../instance/InstanceValidator.java | 28 +++++++++++++++---- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index fdc11fd65..9babd89fd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -73,6 +73,7 @@ import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.Library; import org.hl7.fhir.r5.model.Measure; @@ -868,7 +869,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte // if that failed, we try to expand on the server if (addDependentResources(p, vs)) { - p.addParameter().setName("cache-id").setValue(new StringType(tcc.getCacheId())); + p.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId())); } if (noTerminologyServer) { @@ -1147,7 +1148,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte boolean cached = addDependentResources(p, vs); if (cached) { - p.addParameter().setName("cache-id").setValue(new StringType(tcc.getCacheId())); + p.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId())); } return p; } @@ -1276,7 +1277,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte addDependentResources(pin, vs); } if (cache) { - pin.addParameter().setName("cache-id").setValue(new StringType(tcc.getCacheId())); + pin.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId())); } for (ParametersParameterComponent pp : pin.getParameter()) { if (pp.getName().equals("profile")) { @@ -1917,6 +1918,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte private Set notCanonical = new HashSet(); protected IWorkerContextManager.IPackageLoadingTracker packageTracker; + private boolean forPublication; @Override public Resource fetchResourceById(String type, String uri) { @@ -2467,4 +2469,13 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte // TODO Auto-generated method stub return new PEBuilder(this, elementProps, fixedProps); } + + public boolean isForPublication() { + return forPublication; + } + + public void setForPublication(boolean value) { + forPublication = value; + } + } 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 62950a9c8..6a402c70d 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 @@ -274,6 +274,7 @@ public class ContextUtilities implements ProfileKnowledgeProvider { ProfileUtilities pu = new ProfileUtilities(context, msgs, this); pu.setAutoFixSliceNames(true); pu.setThrowException(false); + pu.setForPublication(context.isForPublication()); if (xverManager == null) { xverManager = new XVerExtensionManager(context); } @@ -282,8 +283,9 @@ public class ContextUtilities implements ProfileKnowledgeProvider { pu.sortDifferential(sd, p, p.getUrl(), errors, true); } pu.setDebug(false); - for (String err : errors) + for (String err : errors) { msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getWebPath(), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); + } 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) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 9b90702ea..b54fd07a9 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -941,4 +941,6 @@ public interface IWorkerContext { public PEBuilder getProfiledElementBuilder(PEElementPropertiesPolicy elementProps, boolean fixedProps); + public boolean isForPublication(); + public void setForPublication(boolean value); } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java index 890cb700d..198cdae79 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java @@ -544,7 +544,7 @@ public class Coding extends DataType implements IBaseCoding, ICompositeType, ICo base = base+"|"+getVersion(); base = base + "#"+getCode(); if (hasDisplay()) - base = base+": "+getDisplay(); + base = base+": '"+getDisplay()+"'"; return base; } 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 093c6a696..92176065a 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 @@ -5968,14 +5968,30 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } if (match) { + boolean update = true; boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", ""))); + if (!isOk) { + // is this a subslice? then we put it in as a replacement + String existingName = ei.definition == null || !ei.definition.hasSliceName() ? null : ei.definition.getSliceName(); + String matchingName = ed.hasSliceName() ? ed.getSliceName() : null; + if (existingName != null && matchingName != null) { + if (matchingName.startsWith(existingName+"/")) { + isOk = true; + } else if (existingName.startsWith(matchingName+"/")) { + update = false; + isOk = true; + } + } + } if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), isOk, I18nConstants.VALIDATION_VAL_PROFILE_MATCHMULTIPLE, profile.getVersionedUrl(), (ei.definition == null || !ei.definition.hasSliceName() ? "" : ei.definition.getSliceName()), (ed.hasSliceName() ? ed.getSliceName() : ""))) { - ei.definition = ed; - if (ei.slice == null) { - ei.index = i; - } else { - ei.index = sliceOffset; - ei.sliceindex = i - (sliceOffset + 1); + if (update) { + ei.definition = ed; + if (ei.slice == null) { + ei.index = i; + } else { + ei.index = sliceOffset; + ei.sliceindex = i - (sliceOffset + 1); + } } } } else if (childUnsupportedSlicing) { From 10a0cc7220a61a5f4e35ffa0c55f801556bc78e3 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 17:40:13 +1000 Subject: [PATCH 05/10] Allow meaningWhenMissing on extension definitions --- .../instance/type/StructureDefinitionValidator.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 9ed1ddc95..6afb3ea5f 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 @@ -196,7 +196,7 @@ public class StructureDefinitionValidator extends BaseValidator { rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path); } - rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), !constraint || !element.hasChild("meaningWhenMissing"), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path); + rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), snapshot || !constraint || !element.hasChild("meaningWhenMissing") || meaningWhenMissingAllowed(element), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path); List types = element.getChildrenByName("type"); Set typeCodes = new HashSet<>(); @@ -316,6 +316,12 @@ public class StructureDefinitionValidator extends BaseValidator { return ok; } + private boolean meaningWhenMissingAllowed(Element element) { + // allowed to use meaningWhenMissing on the root of an element to say what it means when the extension + // is not present. + return "Extension".equals(element.getPath()) || (element.getPath().endsWith(".extension")); + } + private boolean addCharacteristics(Set set, String tc) { switch (tc) { case "boolean" : return addCharacteristicsForType(set); From c48fd2b818e509a4fe2161ea4ece2a71e296af55 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 17:41:03 +1000 Subject: [PATCH 06/10] All to strip all paragraphs from generated html (post-markdown) --- .../tests/SnapShotGenerationXTests.java | 10 ++--- .../org.hl7.fhir.validation/4.0.1/loinc.cache | 19 ++++++++++ .../4.0.1/snomed.cache | 38 +++++++++++++++++++ .../org.hl7.fhir.validation/4.0.1/ucum.cache | 16 ++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java index 203d599c0..901d64d35 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java @@ -180,7 +180,7 @@ public class SnapShotGenerationXTests { else source = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-input.xml")); if (!fail) - expected = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-expected.xml")); + expected = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-output.xml")); if (!Utilities.noString(include)) included = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", include + ".xml")); if (!Utilities.noString(register)) { @@ -471,8 +471,7 @@ public class SnapShotGenerationXTests { pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors, false); if (!errors.isEmpty()) throw new FHIRException(errors.get(0)); - IOUtils.copy(TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml"))); - new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-actual.xml")), test.getOutput()); + new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-output.xml")), test.getOutput()); Assertions.assertTrue(test.expected.equalsDeep(test.output), "Output does not match expected"); } @@ -542,11 +541,10 @@ public class SnapShotGenerationXTests { if (!fail) { test.output = output; UtilitiesXTests.context(version).cacheResource(output); - File dst = new File(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml")); + File dst = new File(UtilitiesXTests.tempFile("snapshot", test.getId() + "-output.xml")); if (dst.exists()) dst.delete(); - IOUtils.copy(TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(dst)); - new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-actual.xml")), output); + new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-output.xml")), output); StructureDefinition t1 = test.expected.copy(); t1.setText(null); StructureDefinition t2 = test.output.copy(); 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 db0a89521..fdb128699 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 @@ -3443,3 +3443,22 @@ v: { "class" : "SERVER_ERROR" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://loinc.org", + "version" : "2.71", + "code" : "29463-7", + "display" : "Body weight" +}, "valueSet" :null, "langs":"[en]", "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" +} +------------------------------------------------------------------------------------- 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 b51a4f158..07ca7028f 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 @@ -2150,3 +2150,41 @@ v: { "version" : "http://snomed.info/sct/900000000000207008/version/20230131" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20210731", + "code" : "27113001", + "display" : "Body weight (observable entity)" +}, "valueSet" :null, "langs":"[en]", "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" : "27113001", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20210731" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20210731", + "code" : "38266002", + "display" : "Entire body as a whole (body structure)" +}, "valueSet" :null, "langs":"[en]", "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" : "Entire body as a whole", + "code" : "38266002", + "system" : "http://snomed.info/sct", + "version" : "http://snomed.info/sct/900000000000207008/version/20210731" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache index f44ea8047..16d6dc302 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ucum.cache @@ -420,3 +420,19 @@ v: { "version" : "2.0.1" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://unitsofmeasure.org", + "code" : "KG" +}, "valueSet" :null, "langs":"[en]", "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: { + "severity" : "error", + "error" : "Error processing Unit: 'KG': The unit \"KG\" is unknown at character 1; Unknown Code 'KG' in the system 'http://unitsofmeasure.org'; The provided code http://unitsofmeasure.org#KG is not in the value set 'http://hl7.org/fhir/ValueSet/@all' (from Tx-Server)", + "class" : "UNKNOWN" +} +------------------------------------------------------------------------------------- From 0bcb2ab268a38a3d17301be398c19103d182bb05 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 17:41:16 +1000 Subject: [PATCH 07/10] Strip all paras --- .../org/hl7/fhir/utilities/Utilities.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 47f3f7b6d..2a578b6ae 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -1951,6 +1951,30 @@ public class Utilities { return p; } + public static String stripAllPara(String p) { + if (noString(p)) { + return ""; + } + p = p.trim(); + if (p.startsWith("

")) { + p = p.substring(3); + } + if (p.endsWith("

")) { + p = p.substring(0, p.length()-4); + } + p = p.replace("

", " "); + p = p.replace("

", ""); + while (p.contains("

Date: Tue, 23 May 2023 17:41:31 +1000 Subject: [PATCH 08/10] release notes --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 13ab15751..df02c718a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,11 +2,15 @@ * Snapshot Generation Changes: ** Check for slicenames without any slicing +** Check that slice names are unique ** Check for additional slicing rules in a set of slices ** Check that the minimum cardinality of a set of slices is correct * Clean up duplicate errors when dates/dateTimes/instants have invalid formats * Handle unknown code systems consistently when validating coded elements +* Handle sub-slicing case where slice matches both the slice definition and the sub-slice definition ## Other code changes * Add support for R4B to loader +* Change type if cache-id parameter + From 6ded6410b643f6a3b548466a001b18e5c8048974 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 May 2023 20:47:49 +1000 Subject: [PATCH 09/10] just produce the output every time --- .../java/org/hl7/fhir/validation/tests/ValidationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0bca11e59..0736a648a 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 @@ -107,7 +107,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } public final static boolean PRINT_OUTPUT_TO_CONSOLE = true; - private static final boolean BUILD_NEW = false; + private static final boolean BUILD_NEW = true; private static final boolean CLONE = true; @Parameters(name = "{index}: id {0}") From 5458c2c78b17d82ab097c22dffd63f508dfa280d Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 23 May 2023 16:16:18 -0400 Subject: [PATCH 10/10] Bump PR with blank line --- RELEASE_NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index df02c718a..0e104971d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -14,3 +14,4 @@ * Add support for R4B to loader * Change type if cache-id parameter +