report slicing information automatically where slicing is based on profile + fix shc support + support codesystem-properties-mode + fix value set validation on profiles + fix wrong entry point on vaildating contained resources with profiles

This commit is contained in:
Grahame Grieve 2021-10-08 08:09:37 +11:00
parent 4ff3c45fca
commit a8ba0ed4e5
8 changed files with 105 additions and 26 deletions

View File

@ -514,6 +514,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
public String[] sliceText;
private boolean slicingHint;
private boolean signpost;
private boolean criticalSignpost;
/**
@ -772,9 +773,10 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
return sliceHtml;
}
public void setSliceHtml(String sliceHtml, String[] text) {
public ValidationMessage setSliceHtml(String sliceHtml, String[] text) {
this.sliceHtml = sliceHtml;
this.sliceText = text;
return this;
}
public String getMessageId() {
@ -795,5 +797,14 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
return this;
}
public boolean isCriticalSignpost() {
return criticalSignpost;
}
public ValidationMessage setCriticalSignpost(boolean criticalSignpost) {
this.criticalSignpost = criticalSignpost;
return this;
}
}

View File

@ -2,7 +2,11 @@ package org.hl7.fhir.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
/*
@ -262,9 +266,9 @@ public class BaseValidator {
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
//FIXME: formatMessage should be done here
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, String html, String[] text) {
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) {
if (!thePass) {
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text);
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical);
}
return thePass;
}

View File

@ -139,6 +139,8 @@ public class IgLoader {
res.cntType = Manager.FhirFormat.XML;
else if (t.getKey().endsWith(".ttl"))
res.cntType = Manager.FhirFormat.TURTLE;
else if (t.getKey().endsWith(".shc"))
res.cntType = Manager.FhirFormat.SHC;
else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map"))
res.cntType = Manager.FhirFormat.TEXT;
else

View File

@ -293,14 +293,10 @@ public class ValidationService {
System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info) + " notes");
for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) {
System.out.println(getIssueSummary(issue));
if (crumbs) {
ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg");
if (vm != null) {
if (vm.sliceText != null) {
for (String s : vm.sliceText) {
System.out.println(" slice info: "+s);
}
}
ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg");
if (vm != null && vm.sliceText != null && (crumbs || vm.isCriticalSignpost())) {
for (String s : vm.sliceText) {
System.out.println(" slice info: "+s);
}
}
}

View File

@ -93,6 +93,7 @@ import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
@ -303,7 +304,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
} else if (item instanceof Element) {
Element e = (Element) item;
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
if (e.getName().equals("contained")) {
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
} else {
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
}
} else
throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
boolean ok = true;
@ -2753,7 +2758,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile()));
for (StructureDefinition sd : badProfiles.keySet()) {
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false,
context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
}
@ -2771,7 +2776,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) {
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
for (StructureDefinition sd : badProfiles.keySet()) {
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
}
} else {
@ -2898,6 +2903,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return "<ul>" + b.toString() + "</ul>";
}
private boolean isCritical(List<ValidationMessage> list) {
for (ValidationMessage vm : list) {
if (vm.isSlicingHint() && vm.isCriticalSignpost()) {
return true;
}
}
return false;
}
private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) {
List<String> res = new ArrayList<String>();
for (ValidationMessage vm : list) {
@ -3305,7 +3319,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rr.setExternal(false);
rr.setStack(nstack);
// rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
System.out.println("-->"+nstack.getLiteralPath());
// System.out.println("-->"+nstack.getLiteralPath());
return rr;
}
if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) {
@ -3675,9 +3689,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ValidatorHostContext shc = hostContext.forSlicing();
boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
if (!pass) {
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString()), null);
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString()), null);
for (String url : shc.getSliceRecords().keySet()) {
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false,
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer),
context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url),
context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__,
url,
@ -3687,6 +3701,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return pass;
}
private boolean isProfile(ElementDefinition slicer) {
if (slicer == null || !slicer.hasSlicing()) {
return false;
}
for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) {
if (t.getType() == DiscriminatorType.PROFILE) {
return true;
}
}
return false;
}
public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
String msg;
boolean ok;
@ -4898,7 +4924,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (ei.additionalSlice && ei.definition != null) {
if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) ||
ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false,
slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
profile == null ? "" : " defined in the profile " + profile.getUrl()),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo),
@ -4944,6 +4970,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return problematicPaths;
}
public List<ElementInfo> listChildren(Element element, NodeStack stack) {
// 1. List the children, and remember their exact path (convenience)
List<ElementInfo> children = new ArrayList<ElementInfo>();
@ -5197,7 +5224,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
// todo: validate everything in this bundle.
}
ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), typeMatchesDefn(resourceName, defn), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, defn.getName(), resourceName);
ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), typeMatchesDefn(resourceName, defn), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, defn.getType(), resourceName, defn.getName());
if (ok) {
if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) {

View File

@ -15,10 +15,12 @@ import org.hl7.fhir.convertors.factory.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.Resource;
@ -35,6 +37,7 @@ import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.utils.NodeStack;
@ -209,13 +212,23 @@ public class StructureDefinitionValidator extends BaseValidator {
String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference");
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) {
Resource vs = context.fetchResource(Resource.class, ref);
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null, I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType());
// just because we can't resolve it directly doesn't mean that terminology server can't. Check with it
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serversSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
if (vs != null) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType());
}
}
}
}
}
private boolean serversSupportsValueSet(String ref) {
ValidationResult vr = context.validateCode(new ValidationOptions().checkValueSetOnly().setVsAsUrl().noClient(), new Coding("http://loinc.org", "5792-7", null), new ValueSet().setUrl(ref));
return vr.getErrorClass() == null;
}
private void validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) {
String code = type.getNamedChildValue("code");
if (code == null && path != null) {

View File

@ -26,11 +26,20 @@ public class ValidatorHostContext {
}
public ValidatorHostContext(Object appContext, Element element) {
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
// no container
}
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
// no container
dump("creating");
}
public ValidatorHostContext(Object appContext, Element element, Element root) {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
// no container
dump("creating");
}
public Object getAppContext() {
return appContext;
@ -52,6 +61,7 @@ public class ValidatorHostContext {
public ValidatorHostContext setRootResource(Element rootResource) {
this.rootResource = rootResource;
dump("setting root resource");
return this;
}
@ -96,6 +106,7 @@ public class ValidatorHostContext {
res.rootResource = resource;
res.resource = element;
res.profile = profile;
res.dump("forContained");
return res;
}
@ -104,6 +115,7 @@ public class ValidatorHostContext {
res.rootResource = element;
res.resource = element;
res.profile = profile;
res.dump("forEntry");
return res;
}
@ -113,6 +125,7 @@ public class ValidatorHostContext {
res.rootResource = rootResource;
res.profile = profile;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
res.dump("forProfile "+profile.getUrl());
return res;
}
@ -122,15 +135,24 @@ public class ValidatorHostContext {
res.rootResource = resource;
res.profile = profile;
res.checkSpecials = false;
res.dump("forLocalReference "+profile.getUrl());
return res;
}
private void dump(String ctxt) {
// System.out.println("** app = "+(appContext == null ? "(null)" : appContext.toString())+", res = "+resource.toString()+", root = "+rootResource.toString()+" ("+ctxt+")");
// if (rootResource.getName().equals("contained")) {
// System.out.println("** something is wrong!");
// }
}
public ValidatorHostContext forRemoteReference(StructureDefinition profile, Element resource) {
ValidatorHostContext res = new ValidatorHostContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.profile = profile;
res.checkSpecials = false;
res.dump("forRemoteReference "+profile.getUrl());
return res;
}
@ -141,6 +163,7 @@ public class ValidatorHostContext {
res.profile = profile;
res.checkSpecials = false;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
res.dump("forSlicing");
return res;
}