Check for erroneous attempts to slice or re-slice + add checking for getting slicing minimum cardinality right

This commit is contained in:
Grahame Grieve 2023-05-22 17:14:16 +10:00
parent 3cab5f5ae3
commit 6e3171d85f
1 changed files with 68 additions and 5 deletions

View File

@ -8,14 +8,20 @@ import java.util.Set;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ElementRedirection; import org.hl7.fhir.r5.conformance.ElementRedirection;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@ -267,12 +273,28 @@ public class ProfilePathProcessor {
if (!diffMatches.get(0).hasSlicing()) if (!diffMatches.get(0).hasSlicing())
outcome.setSlicing(profileUtilities.makeExtensionSlicing()); outcome.setSlicing(profileUtilities.makeExtensionSlicing());
else else {
outcome.setSlicing(diffMatches.get(0).getSlicing().copy()); outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
for (int i = 1; i < diffMatches.size(); i++) {
if (diffMatches.get(i).hasSlicing()) {
if (!slicingMatches(diffMatches.get(0).getSlicing(), diffMatches.get(i).getSlicing())) {
profileUtilities.getMessages().add(new ValidationMessage(Source.InstanceValidator, ValidationMessage.IssueType.BUSINESSRULE, diffMatches.get(0).getPath(),
profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_CHANGE_SLICING, diffMatches.get(0).getId(), slicingSummary(diffMatches.get(0).getSlicing()), diffMatches.get(i).getId(), slicingSummary(diffMatches.get(i).getSlicing())),
ValidationMessage.IssueSeverity.ERROR));
} else {
profileUtilities.getMessages().add(new ValidationMessage(Source.InstanceValidator, ValidationMessage.IssueType.BUSINESSRULE, diffMatches.get(0).getPath(),
profileUtilities.getContext().formatMessage(I18nConstants.ATTEMPT_TO_CHANGE_SLICING, diffMatches.get(0).getId(), diffMatches.get(i).getId()),
IssueSeverity.INFORMATION));
}
}
}
}
if (cursors.resultPathBase != null) { if (cursors.resultPathBase != null) {
if (!outcome.getPath().startsWith(cursors.resultPathBase)) if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH));
} }
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
slicerElement = outcome; slicerElement = outcome;
@ -337,6 +359,20 @@ public class ProfilePathProcessor {
cursors.diffCursor = newDiffLimit + 1; cursors.diffCursor = newDiffLimit + 1;
} }
private String slicingSummary(ElementDefinitionSlicingComponent s) {
return s.toString();
}
private boolean slicingMatches(ElementDefinitionSlicingComponent s1, ElementDefinitionSlicingComponent s2) {
if ((!s1.hasOrdered() && s2.hasOrdered()) || (s1.hasOrdered() && s2.hasOrdered() && !Base.compareDeep(s1.getOrderedElement(), s2.getOrderedElement(), false))) {
return false;
}
if ((!s1.hasRules() && s2.hasRules()) || (s1.hasRules() && s2.hasRules() && !Base.compareDeep(s1.getRulesElement(), s2.getRulesElement(), false))) {
return false;
}
return Base.compareDeep(s1.getDiscriminator(), s2.getDiscriminator(), false);
}
private void processSimplePathWhereDiffsConstrainTypes(String currentBasePath, List<ElementDefinition> diffMatches, List<TypeSlice> typeList, ProfilePathProcessorState cursors) { private void processSimplePathWhereDiffsConstrainTypes(String currentBasePath, List<ElementDefinition> diffMatches, List<TypeSlice> typeList, ProfilePathProcessorState cursors) {
int start = 0; int start = 0;
int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor);
@ -344,13 +380,14 @@ public class ProfilePathProcessor {
ElementDefinition elementToRemove = null; ElementDefinition elementToRemove = null;
boolean shortCut = !typeList.isEmpty() && typeList.get(0).getType() != null; boolean shortCut = !typeList.isEmpty() && typeList.get(0).getType() != null;
// we come here whether they are sliced in the diff, or whether the short cut is used. // we come here whether they are sliced in the diff, or whether the short cut is used.
String path = diffMatches.get(0).getPath();
if (shortCut) { if (shortCut) {
// this is the short cut method, we've just dived in and specified a type slice. // this is the short cut method, we've just dived in and specified a type slice.
// in R3 (and unpatched R4, as a workaround right now... // in R3 (and unpatched R4, as a workaround right now...
if (!VersionUtilities.isR4Plus(profileUtilities.getContext().getVersion()) || !profileUtilities.isNewSlicingProcessing()) { // newSlicingProcessing is a work around for editorial loop dependency if (!VersionUtilities.isR4Plus(profileUtilities.getContext().getVersion()) || !profileUtilities.isNewSlicingProcessing()) { // newSlicingProcessing is a work around for editorial loop dependency
// we insert a cloned element with the right types at the start of the diffMatches // we insert a cloned element with the right types at the start of the diffMatches
ElementDefinition ed = new ElementDefinition(); ElementDefinition ed = new ElementDefinition();
ed.setPath(profileUtilities.determineTypeSlicePath(diffMatches.get(0).getPath(), currentBasePath)); ed.setPath(profileUtilities.determineTypeSlicePath(path, currentBasePath));
for (TypeSlice ts : typeList) for (TypeSlice ts : typeList)
ed.addType().setCode(ts.getType()); ed.addType().setCode(ts.getType());
ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent());
@ -365,7 +402,7 @@ public class ProfilePathProcessor {
// so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type.
// see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element
ElementDefinition ed = new ElementDefinition(); ElementDefinition ed = new ElementDefinition();
ed.setPath(profileUtilities.determineTypeSlicePath(diffMatches.get(0).getPath(), currentBasePath)); ed.setPath(profileUtilities.determineTypeSlicePath(path, currentBasePath));
ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); ed.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent());
ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); ed.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this");
ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED); ed.getSlicing().setRules(ElementDefinition.SlicingRules.CLOSED);
@ -374,6 +411,13 @@ public class ProfilePathProcessor {
getDifferential().getElement().add(newDiffCursor, ed); getDifferential().getElement().add(newDiffCursor, ed);
elementToRemove = ed; elementToRemove = ed;
} }
} else { // if it's not a short cut, then the path has to be correct
String t1 = currentBasePath.substring(currentBasePath.lastIndexOf(".")+1);
String t2 = path.substring(path.lastIndexOf(".")+1);
if (!t1.equals(t2)) {
throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.ED_PATH_WRONG_TYPE_MATCH, path.replace(t2, t1), path));
}
} }
int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor); int newDiffLimit = profileUtilities.findEndOfElement(getDifferential(), newDiffCursor);
// the first element is setting up the slicing // the first element is setting up the slicing
@ -429,7 +473,7 @@ public class ProfilePathProcessor {
.processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor, .processPaths(new ProfilePathProcessorState(cursors.base, cursors.baseCursor, newDiffCursor,
cursors.contextName, cursors.resultPathBase)); cursors.contextName, cursors.resultPathBase));
if (elementDefinition == null) if (elementDefinition == null)
throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath())); throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, path));
// now set up slicing on the e (cause it was wiped by what we called. // now set up slicing on the e (cause it was wiped by what we called.
elementDefinition.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent()); elementDefinition.setSlicing(new ElementDefinition.ElementDefinitionSlicingComponent());
elementDefinition.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this"); elementDefinition.getSlicing().addDiscriminator().setType(ElementDefinition.DiscriminatorType.TYPE).setPath("$this");
@ -607,6 +651,7 @@ public class ProfilePathProcessor {
cursors.resultPathBase = outcome.getPath(); cursors.resultPathBase = outcome.getPath();
else if (!outcome.getPath().startsWith(cursors.resultPathBase)) else if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH));
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
cursors.baseCursor++; cursors.baseCursor++;
cursors.diffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)) + 1; cursors.diffCursor = getDifferential().getElement().indexOf(diffMatches.get(0)) + 1;
@ -731,6 +776,7 @@ public class ProfilePathProcessor {
cursors.resultPathBase = outcome.getPath(); cursors.resultPathBase = outcome.getPath();
else if (!outcome.getPath().startsWith(cursors.resultPathBase)) else if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__, outcome.getPath(), cursors.resultPathBase)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__, outcome.getPath(), cursors.resultPathBase));
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), true)) { if (profileUtilities.hasInnerDiffMatches(getDifferential(), currentBasePath, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), true)) {
// well, the profile walks into this, so we need to as well // well, the profile walks into this, so we need to as well
@ -756,6 +802,9 @@ public class ProfilePathProcessor {
} }
} }
} }
if (!profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) {
cursors.diffCursor++;
}
int start = cursors.diffCursor; int start = cursors.diffCursor;
while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + ".")) while (getDifferential().getElement().size() > cursors.diffCursor && profileUtilities.pathStartsWith(getDifferential().getElement().get(cursors.diffCursor).getPath(), currentBasePath + "."))
cursors.diffCursor++; cursors.diffCursor++;
@ -807,7 +856,7 @@ public class ProfilePathProcessor {
} }
cursors.contextName = dt.getUrl(); cursors.contextName = dt.getUrl();
if (getRedirector() == null || getRedirector().isEmpty()) { if (getRedirector() == null || getRedirector().isEmpty()) {
this this
.incrementDebugIndent() .incrementDebugIndent()
.withBaseLimit(dt.getSnapshot().getElement().size() - 1) .withBaseLimit(dt.getSnapshot().getElement().size() - 1)
@ -893,6 +942,7 @@ public class ProfilePathProcessor {
diffMatches.get(0).setUserData(profileUtilities.UD_GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called diffMatches.get(0).setUserData(profileUtilities.UD_GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called
} }
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
@ -942,6 +992,8 @@ public class ProfilePathProcessor {
int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor);
for (int i = cursors.baseCursor + 1; i <= newBaseLimit; i++) { for (int i = cursors.baseCursor + 1; i <= newBaseLimit; i++) {
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(i).copy()); outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(i).copy());
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
} }
} }
@ -978,6 +1030,7 @@ public class ProfilePathProcessor {
cursors.diffCursor = newDiffLimit + 1; cursors.diffCursor = newDiffLimit + 1;
diffpos++; diffpos++;
} else { } else {
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
cursors.baseCursor++; cursors.baseCursor++;
// just copy any children on the base // just copy any children on the base
@ -988,6 +1041,7 @@ public class ProfilePathProcessor {
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH));
outcome.setUserData(profileUtilities.UD_BASE_PATH, outcome.getPath()); outcome.setUserData(profileUtilities.UD_BASE_PATH, outcome.getPath());
outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl()); outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl());
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
cursors.baseCursor++; cursors.baseCursor++;
} }
@ -1016,6 +1070,7 @@ public class ProfilePathProcessor {
outcome.setMin(0); // we're in a slice, so it's only a mandatory if it's explicitly marked so outcome.setMin(0); // we're in a slice, so it's only a mandatory if it's explicitly marked so
if (!outcome.getPath().startsWith(cursors.resultPathBase)) if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH));
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived()); profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived());
profileUtilities.removeStatusExtensions(outcome); profileUtilities.removeStatusExtensions(outcome);
@ -1079,6 +1134,12 @@ public class ProfilePathProcessor {
cursors.baseCursor++; cursors.baseCursor++;
} }
private void debugCheck(ElementDefinition outcome) {
if (outcome.getPath().startsWith("List.") && "http://nictiz.nl/fhir/StructureDefinition/Bundle-MedicationOverview".equals(url)) {
System.out.println("wrong!");
}
}
private void processPathWithSlicedBaseWhereDiffsConstrainTypes(String currentBasePath, List<ElementDefinition> diffMatches, List<TypeSlice> typeList, ProfilePathProcessorState cursors) { private void processPathWithSlicedBaseWhereDiffsConstrainTypes(String currentBasePath, List<ElementDefinition> diffMatches, List<TypeSlice> typeList, ProfilePathProcessorState cursors) {
int start = 0; int start = 0;
int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor); int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor);
@ -1252,6 +1313,7 @@ public class ProfilePathProcessor {
cursors.resultPathBase = outcome.getPath(); cursors.resultPathBase = outcome.getPath();
else if (!outcome.getPath().startsWith(cursors.resultPathBase)) else if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH));
debugCheck(outcome);
getResult().getElement().add(outcome); getResult().getElement().add(outcome);
// the profile walks into this, so we need to as well // the profile walks into this, so we need to as well
// did we implicitly step into a new type? // did we implicitly step into a new type?
@ -1295,6 +1357,7 @@ public class ProfilePathProcessor {
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource())); outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
if (!outcome.getPath().startsWith(cursors.resultPathBase)) if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, getProfileName(), outcome.getPath(), cursors.resultPathBase)); throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, getProfileName(), outcome.getPath(), cursors.resultPathBase));
debugCheck(outcome);
getResult().getElement().add(outcome); // so we just copy it in getResult().getElement().add(outcome); // so we just copy it in
outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl()); outcome.setUserData(profileUtilities.UD_BASE_MODEL, getSourceStructureDefinition().getUrl());
outcome.setUserData(profileUtilities.UD_BASE_PATH, cursors.resultPathBase); outcome.setUserData(profileUtilities.UD_BASE_PATH, cursors.resultPathBase);