track slice validation for users to resolve slicing issues
This commit is contained in:
parent
cb1fa1dad9
commit
1c3061dbf7
|
@ -487,6 +487,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
|
|||
private String html;
|
||||
private String locationLink;
|
||||
private String txLink;
|
||||
private boolean slicingHint;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -746,5 +747,14 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
|
|||
this.html = html;
|
||||
}
|
||||
|
||||
public boolean isSlicingHint() {
|
||||
return slicingHint;
|
||||
}
|
||||
|
||||
public ValidationMessage setSlicingHint(boolean slicingHint) {
|
||||
this.slicingHint = slicingHint;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -150,6 +150,27 @@ public class BaseValidator {
|
|||
return thePass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails. And mark it as a slicing hint for later recovery if appropriate
|
||||
*
|
||||
* @param thePass
|
||||
* Set this parameter to <code>false</code> if the validation does not pass
|
||||
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
|
||||
*/
|
||||
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
|
||||
if (!thePass) {
|
||||
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION).setSlicingHint(true);
|
||||
}
|
||||
return thePass;
|
||||
}
|
||||
|
||||
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, String html) {
|
||||
if (!thePass) {
|
||||
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION).setSlicingHint(true).setHtml(html);
|
||||
}
|
||||
return thePass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
|
||||
*
|
||||
|
@ -168,8 +189,7 @@ public class BaseValidator {
|
|||
protected boolean txHint(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
|
||||
if (!thePass) {
|
||||
String message = formatMessage(theMessage, theMessageArguments);
|
||||
addValidationMessage(errors, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine)
|
||||
.setTxLink(txLink);
|
||||
addValidationMessage(errors, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine).setTxLink(txLink);
|
||||
}
|
||||
return thePass;
|
||||
}
|
||||
|
@ -338,9 +358,9 @@ public class BaseValidator {
|
|||
|
||||
}
|
||||
|
||||
protected void addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity) {
|
||||
protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity) {
|
||||
Source source = this.source;
|
||||
addValidationMessage(errors, type, line, col, path, msg, theSeverity, source);
|
||||
return addValidationMessage(errors, type, line, col, path, msg, theSeverity, source);
|
||||
}
|
||||
|
||||
protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource) {
|
||||
|
|
|
@ -207,6 +207,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
private Element rootResource;
|
||||
private StructureDefinition profile; // the profile that contains the content being validated
|
||||
private boolean checkSpecials = true;
|
||||
private Map<String, List<ValidationMessage>> sliceRecords;
|
||||
|
||||
public ValidatorHostContext(Object appContext) {
|
||||
this.appContext = appContext;
|
||||
|
@ -242,6 +243,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
res.rootResource = rootResource;
|
||||
res.container = container;
|
||||
res.profile = profile;
|
||||
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -277,6 +279,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
return resource;
|
||||
}
|
||||
|
||||
public void sliceNotes(String url, List<ValidationMessage> record) {
|
||||
sliceRecords.put(url, record);
|
||||
}
|
||||
|
||||
public ValidatorHostContext forSlicing() {
|
||||
ValidatorHostContext res = new ValidatorHostContext(appContext);
|
||||
res.resource = resource;
|
||||
res.rootResource = resource;
|
||||
res.container = resource;
|
||||
res.profile = profile;
|
||||
res.checkSpecials = false;
|
||||
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -405,8 +422,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
} else
|
||||
throw new NotImplementedException("Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element");
|
||||
boolean ok = true;
|
||||
for (ValidationMessage v : valerrors)
|
||||
List<ValidationMessage> record = new ArrayList<>();
|
||||
for (ValidationMessage v : valerrors) {
|
||||
ok = ok && !v.getLevel().isError();
|
||||
if (v.getLevel().isError() || v.isSlicingHint()) {
|
||||
record.add(v);
|
||||
}
|
||||
}
|
||||
if (!ok && !record.isEmpty()) {
|
||||
ctxt.sliceNotes(url, record);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
@ -2353,7 +2378,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
if (!isShowMessagesFromReferences()) {
|
||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), "Unable to find matching profile for "+ref+" among choices: " + asList(type.getTargetProfile()));
|
||||
for (StructureDefinition sd : badProfiles.keySet()) {
|
||||
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+sd.getUrl()+" does not match for "+ref+" because of the following errors: "+errorSummary(badProfiles.get(sd)));
|
||||
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+sd.getUrl()+" does not match for "+ref+" because of the following errors: "+errorSummaryForSlicing(badProfiles.get(sd)), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
|
||||
}
|
||||
} else {
|
||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size()==1, "Unable to find matching profile for "+ref+" among choices: " + asList(type.getTargetProfile()));
|
||||
|
@ -2369,7 +2394,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
if (!isShowMessagesFromReferences()) {
|
||||
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for "+ref+" among choices: " + asListByUrl(goodProfiles.keySet()));
|
||||
for (StructureDefinition sd : badProfiles.keySet()) {
|
||||
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+sd.getUrl()+" does not match for "+ref+" because of the following errors: "+errorSummary(badProfiles.get(sd)));
|
||||
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+sd.getUrl()+" does not match for "+ref+" because of the following errors: "+errorSummaryForSlicing(badProfiles.get(sd)), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
|
||||
}
|
||||
} else {
|
||||
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for "+ref+" among choices: " + asListByUrl(goodProfiles.keySet()));
|
||||
|
@ -2434,16 +2459,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
return true;
|
||||
}
|
||||
|
||||
private String errorSummary(List<ValidationMessage> list) {
|
||||
private String errorSummaryForSlicing(List<ValidationMessage> list) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (ValidationMessage vm : list) {
|
||||
if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
|
||||
if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
|
||||
b.append(vm.getLocation()+": "+vm.getMessage());
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String errorSummaryForSlicingAsHtml(List<ValidationMessage> list) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (ValidationMessage vm : list) {
|
||||
if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
|
||||
b.append("<li>"+vm.getLocation()+": "+vm.getHtml()+"</li>");
|
||||
}
|
||||
}
|
||||
return "<ul>"+b.toString()+"</ul>";
|
||||
}
|
||||
|
||||
private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) {
|
||||
for (TypeRefComponent tr : types) {
|
||||
if ("Reference".equals(tr.getCode())) {
|
||||
|
@ -3186,7 +3221,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
* @throws IOException
|
||||
* @throws FHIRException
|
||||
*/
|
||||
private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, NodeStack stack) throws DefinitionException, FHIRException {
|
||||
private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack) throws DefinitionException, FHIRException {
|
||||
if (!slicer.getSlicing().hasDiscriminator())
|
||||
return false; // cannot validate in this case
|
||||
|
||||
|
@ -3221,7 +3256,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
} else
|
||||
throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + ed.getId() + " in "+profile.getUrl()+" has no types");
|
||||
if (discriminator.isEmpty())
|
||||
expression.append(" and this is " + type);
|
||||
expression.append(" and $this is " + type);
|
||||
else
|
||||
expression.append(" and " + discriminator + " is " + type);
|
||||
} else if (s.getType() == DiscriminatorType.PROFILE) {
|
||||
|
@ -3281,7 +3316,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
ed.setUserData("slice.expression.cache", n);
|
||||
}
|
||||
|
||||
return evaluateSlicingExpression(hostContext, element, path, profile, n);
|
||||
ValidatorHostContext shc = hostContext.forSlicing();
|
||||
boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
|
||||
if (!pass) {
|
||||
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Does not match slice'"+ed.getSliceName()+"' (discriminator = "+n.toString()+")");
|
||||
for (String url : shc.sliceRecords.keySet()) {
|
||||
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+url+" does not match for "+stack.getLiteralPath()+" because of the following profile issues: "+errorSummaryForSlicing(shc.sliceRecords.get(url)),
|
||||
"Profile "+url+" does not match for "+stack.getLiteralPath()+" because of the following profile issues: "+errorSummaryForSlicingAsHtml(shc.sliceRecords.get(url)));
|
||||
}
|
||||
}
|
||||
return pass;
|
||||
}
|
||||
|
||||
public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
|
||||
|
@ -5018,7 +5062,10 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
|||
|
||||
// if (process) {
|
||||
for (ElementInfo ei : children) {
|
||||
unsupportedSlicing = matchSlice(hostContext, errors, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei);
|
||||
if (ei.sliceInfo == null) {
|
||||
ei.sliceInfo = new ArrayList<>();
|
||||
}
|
||||
unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
@ -5032,9 +5079,11 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
|||
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" */) {
|
||||
hint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " for the profile " + profile.getUrl()));
|
||||
slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " defined in the profile " + profile.getUrl()+": "+errorSummaryForSlicing(ei.sliceInfo)),
|
||||
"This element does not match any known slice" + (profile == null ? "" : " defined in the profile " + profile.getUrl()+": "+errorSummaryForSlicingAsHtml(ei.sliceInfo)));
|
||||
} else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) {
|
||||
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " for profile " + profile.getUrl() + " and slicing is CLOSED"));
|
||||
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice " + (profile == null ? "" : " defined in the profile " + profile.getUrl() + " and slicing is CLOSED: "+errorSummaryForSlicing(ei.sliceInfo)),
|
||||
"This element does not match any known slice " + (profile == null ? "" : " defined in the profile " + profile.getUrl() + " and slicing is CLOSED: "+errorSummaryForSlicingAsHtml(ei.sliceInfo)));
|
||||
}
|
||||
} else {
|
||||
// Don't raise this if we're in an abstract profile, like Resource
|
||||
|
@ -5081,7 +5130,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
|||
checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited);
|
||||
}
|
||||
|
||||
public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, NodeStack stack,
|
||||
public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack,
|
||||
ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed,
|
||||
boolean childUnsupportedSlicing, ElementInfo ei) {
|
||||
boolean match = false;
|
||||
|
@ -5090,7 +5139,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
|||
} else {
|
||||
if (nameMatches(ei.name, tail(ed.getPath())))
|
||||
try {
|
||||
match = sliceMatches(hostContext, ei.element, ei.path, slicer, ed, profile, errors, stack);
|
||||
match = sliceMatches(hostContext, ei.element, ei.path, slicer, ed, profile, errors, sliceInfo, stack);
|
||||
if (match) {
|
||||
ei.slice = slicer;
|
||||
|
||||
|
@ -5557,6 +5606,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
|||
|
||||
public class ElementInfo {
|
||||
|
||||
public List<ValidationMessage> sliceInfo;
|
||||
public int index; // order of definition in overall order. all slices get the index of the slicing definition
|
||||
public int sliceindex; // order of the definition in the slices (if slice != null)
|
||||
public int count;
|
||||
|
|
Loading…
Reference in New Issue