fixes to snapshot generation and validation for Bundle.tnry slicing by resource profile

This commit is contained in:
Grahame Grieve 2020-01-15 17:55:24 +11:00
parent 9f1697dad1
commit c13de56203
2 changed files with 173 additions and 31 deletions

View File

@ -248,6 +248,8 @@ public class ProfileUtilities extends TranslatingUtilities {
public static final String IS_DERIVED = "derived.fact";
public static final String UD_ERROR_STATUS = "error-status";
private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
private static final boolean COPY_BINDING_EXTENSIONS = false;
private static final boolean DONT_DO_THIS = false;
private final boolean ADD_REFERENCE_TO_TABLE = true;
private boolean useTableForFixedValues = true;
@ -574,6 +576,34 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
}
// last, check for wrong profiles or target profiles
for (ElementDefinition ed : derived.getSnapshot().getElement()) {
for (TypeRefComponent t : ed.getType()) {
for (UriType u : t.getProfile()) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
if (sd == null) {
if (messages != null) {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING));
}
} else {
String wt = t.getWorkingCode();
if (ed.getPath().equals("Bundle.entry.response.outcome")) {
wt = "OperationOutcome";
}
if (!sd.getType().equals(wt)) {
boolean ok = isCompatibleType(wt, sd.getType());
if (!ok) {
String smsg = "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt;
if (exception)
throw new DefinitionException(smsg);
else
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), smsg, IssueSeverity.ERROR));
}
}
}
}
}
}
} catch (Exception e) {
// if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
derived.setSnapshot(null);
@ -581,6 +611,18 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
private boolean isCompatibleType(String base, String type) {
StructureDefinition sd = context.fetchTypeDefinition(type);
while (sd != null) {
if (sd.getType().equals(base)) {
return true;
}
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
}
return false;
}
private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
for (ElementDefinition sed : source.getElement()) {
@ -2293,10 +2335,25 @@ public class ProfileUtilities extends TranslatingUtilities {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
}
}
base.setBinding(derived.getBinding().copy());
ElementDefinitionBindingComponent d = derived.getBinding();
ElementDefinitionBindingComponent nb = base.getBinding().copy();
if (!COPY_BINDING_EXTENSIONS) {
nb.getExtension().clear();
}
nb.setDescription(null);
nb.getExtension().addAll(d.getExtension());
if (d.hasStrength()) {
nb.setStrength(d.getStrength());
}
if (d.hasDescription()) {
nb.setDescription(d.getDescription());
}
if (d.hasValueSet()) {
nb.setValueSet(d.getValueSet());
}
base.setBinding(nb);
} else if (trimDifferential)
derived.setBinding(null);
else
@ -2316,32 +2373,62 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (derived.hasType()) {
// if (derived.getPath().endsWith("[x]") && derived.hasSlicing() && derived.getSlicing().getRules() != SlicingRules.CLOSED) {
// // if we're slicing, and not closed, then it's the same list
//
// derived.getType().clear();
// derived.getType().addAll(base.getType());
// } else
if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
if (base.hasType()) {
for (TypeRefComponent ts : derived.getType()) {
// if (!ts.hasCode()) { // ommitted in the differential; copy it over....
// if (base.getType().size() > 1)
// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")");
// if (base.getType().get(0).getCode() != null)
// ts.setCode(base.getType().get(0).getCode());
// }
boolean ok = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
String t = ts.getWorkingCode();
for (TypeRefComponent td : base.getType()) {;
String tt = td.getWorkingCode();
b.append(tt);
if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work around for old badly generated SDs
"Element".equals(tt) || "*".equals(tt) ||
(("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t)))))
if (td.hasCode() && (tt.equals(t))) {
ok = true;
}
if (!ok) {
StructureDefinition sdt = context.fetchTypeDefinition(tt);
if (sdt != null && sdt.getAbstract()) {
StructureDefinition sdb = context.fetchTypeDefinition(t);
while (sdb != null && !ok) {
ok = sdb.getType().equals(sdt.getUrl());
sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition());
}
}
}
// work around for old badly generated SDs
if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
ok = true;
}
if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
ok = true;
}
if (ok && ts.hasTargetProfile()) {
// check that any derived target has a reference chain back to one of the base target profiles
for (UriType u : ts.getTargetProfile()) {
boolean tgtOk = false;
String url = u.getValue();
while (url != null && !td.hasTargetProfile(url)) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
if (messages != null) {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl+"#"+derived.getPath(), "Connect check whether the target profile "+url+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
}
url = null;
tgtOk = true; // suppress error message
} else {
url = sd.getBaseDefinition();
}
}
if (!tgtOk) {
if (messages == null) {
throw new FHIRException("Error at "+purl+"#"+derived.getPath()+": The target profile "+url+" is not valid constraint on the base ("+td.getTargetProfile()+")");
} else {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "The target profile "+url+" is not valid constraint on the base ("+td.getTargetProfile()+")", IssueSeverity.ERROR));
}
}
}
}
}
if (!ok)
throw new DefinitionException("StructureDefinition "+purl+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());
}

View File

@ -4287,6 +4287,16 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException, IOException {
String resourceName = element.getType();
TypeRefComponent trr = null;
for (TypeRefComponent tr : child.getType()) {
if (tr.getCode().equals("Resource")) {
trr = tr;
break;
}
}
if (trr == null) {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, "The type '"+resourceName+" is not valid - no resources allowed here");
} else if (isValidResourceType(resourceName, trr)) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
sdTime = sdTime + (System.nanoTime() - t);
@ -4298,9 +4308,53 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
} else {
hc = hostContext.forContained(element);
}
if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'"))
if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'")) {
validateResource(hc, errors, resource, element, profile, null, idstatus, stack, false);
}
} else {
List<String> types = new ArrayList<>();
for (UriType u : trr.getProfile()) {
StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue());
if (sd != null && !types.contains(sd.getType())) {
types.add(sd.getType());
}
}
if (types.size() == 1) {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, "The type '"+resourceName+"' is not valid - must be "+types.get(0));
} else {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, "The type '"+resourceName+"' is not valid - must be one of "+types);
}
}
}
private boolean isValidResourceType(String type, TypeRefComponent def) {
if (!def.hasProfile()) {
return true;
}
List<StructureDefinition> list = new ArrayList<>();
for (UriType u : def.getProfile()) {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue());
if (sdt != null) {
list.add(sdt);
}
}
StructureDefinition sdt = context.fetchTypeDefinition(type);
while (sdt != null) {
if (def.getWorkingCode().equals("Resource")) {
for (StructureDefinition sd : list) {
if (sd.getUrl().equals(sdt.getUrl())) {
return true;
}
if (sd.getType().equals(sdt.getType())) {
return true;
}
}
}
sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition());
}
return false;
}
private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) {
// first entry must be a composition
@ -4480,6 +4534,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
checkInvariants(hostContext, errors, profile, ei.definition, resource, ei.element, localStack, true);
ei.element.markValidation(profile, ei.definition);
boolean elementValidated = false;
if (type != null) {
if (isPrimitiveType(type)) {
checkPrimitive(hostContext, errors, ei.path, type, ei.definition, ei.element, profile, stack);
@ -4514,6 +4569,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
}
} else if (type.equals("Resource")) {
validateContains(hostContext, errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if
elementValidated = true;
// (str.matches(".*([.,/])work\\1$"))
} else if (Utilities.isAbsoluteUrl(type)) {
StructureDefinition defn = context.fetchTypeDefinition(type);
@ -4532,7 +4588,6 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
validateElement(hostContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false, true, null);
}
StructureDefinition p = null;
boolean elementValidated = false;
String tail = null;
if (profiles.isEmpty()) {
if (type != null) {