Add check for UCUM annotations + add ValidationControl for hosting the validator in external processes

This commit is contained in:
Grahame Grieve 2020-09-07 13:06:43 +10:00
parent f7b0e03036
commit b47a1da054
5 changed files with 109 additions and 13 deletions

View File

@ -605,3 +605,4 @@ FHIRPATH_NUMERICAL_ONLY = Error evaluating FHIRPath expression: The function {0}
FHIRPATH_DECIMAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on a decimal but found {1}
FHIRPATH_FOCUS_PLURAL = Error evaluating FHIRPath expression: focus for {0} has more than one value
REFERENCE_REF_SUSPICIOUS = The syntax of the reference ''{0}'' looks incorrect, and it should be checked
TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable

View File

@ -2,7 +2,12 @@ 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;
/*
Copyright (c) 2011+, HL7, Inc.
@ -64,6 +69,8 @@ POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
@ -78,10 +85,28 @@ import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
import org.hl7.fhir.validation.instance.utils.IndexedElement;
public class BaseValidator {
public class ValidationControl {
private boolean allowed;
private IssueSeverity level;
public ValidationControl(boolean allowed, IssueSeverity level) {
super();
this.allowed = allowed;
this.level = level;
}
public boolean isAllowed() {
return allowed;
}
public IssueSeverity getLevel() {
return level;
}
}
protected final String META = "meta";
protected final String ENTRY = "entry";
protected final String DOCUMENT = "document";
@ -98,7 +123,16 @@ public class BaseValidator {
protected Source source;
protected IWorkerContext context;
protected TimeTracker timeTracker = new TimeTracker();
/**
* Use to control what validation the validator performs.
* Using this, you can turn particular kinds of validation on and off
* In addition, you can override the error | warning | hint level and make it a different level
*
* There is no way to do this using the command line validator; it's a service that is only
* offered when the validator is hosted in some other process
*/
private Map<String, ValidationControl> validationControl = new HashMap<>();
public BaseValidator(IWorkerContext context){
this.context = context;
@ -287,7 +321,10 @@ public class BaseValidator {
protected boolean txRule(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String message = context.formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setTxLink(txLink));
ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(theMessage);
if (checkMsgId(theMessage, vm)) {
errors.add(vm.setTxLink(txLink));
}
}
return thePass;
}
@ -415,10 +452,23 @@ public class BaseValidator {
protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource, String id) {
ValidationMessage validationMessage = new ValidationMessage(theSource, type, line, col, path, msg, theSeverity).setMessageId(id);
errors.add(validationMessage);
if (checkMsgId(id, validationMessage)) {
errors.add(validationMessage);
}
return validationMessage;
}
public boolean checkMsgId(String id, ValidationMessage vm) {
if (id != null && validationControl.containsKey(id)) {
ValidationControl control = validationControl.get(id);
if (control.level != null) {
vm.setLevel(control.level);
}
return control.isAllowed();
}
return true;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
@ -429,7 +479,10 @@ public class BaseValidator {
protected boolean txWarning(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass) {
String nmsg = context.formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg));
ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg);
if (checkMsgId(msg, vmsg)) {
errors.add(vmsg);
}
}
return thePass;
@ -567,7 +620,10 @@ public class BaseValidator {
}
protected void addValidationMessage(List<ValidationMessage> errors, IssueType type, String path, String msg, String html, IssueSeverity theSeverity, String id) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity).setMessageId(id));
ValidationMessage vm = new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity);
if (checkMsgId(id, vm)) {
errors.add(vm.setMessageId(id));
}
}
/**
@ -802,5 +858,9 @@ public class BaseValidator {
}
}
public Map<String, ValidationControl> getValidationControl() {
return validationControl;
}
}

View File

@ -34,6 +34,7 @@ import org.hl7.fhir.r5.utils.*;
import org.hl7.fhir.r5.utils.IResourceValidator.*;
import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.utilities.IniFile;
@ -309,6 +310,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
private List<ImplementationGuide> igs = new ArrayList<>();
private boolean showTimes;
private List<BundleValidationRule> bundleValidationRules = new ArrayList<>();
private Map<String, ValidationControl> validationControl = new HashMap<>();
private class AsteriskFilter implements FilenameFilter {
String dir;
@ -1580,6 +1582,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
validator.setFetcher(this);
validator.getImplementationGuides().addAll(igs);
validator.getBundleValidationRules().addAll(bundleValidationRules);
validator.getValidationControl().putAll(validationControl );
return validator;
}
@ -2391,9 +2394,27 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
return pcm.packageExists(id, ver);
}
public void loadPackage(String id, String ver) throws IOException, FHIRException {
loadIg(id+(ver == null ? "" : "#"+ver), true);
}
/**
* Systems that host the ValidationEngine can use this to control what validation the validator performs.
*
* Using this, you can turn particular kinds of validation on and off. In addition, you can override
* the error | warning | hint level and make it a different level.
*
* Each entry has
* * 'allowed': a boolean flag. if this is false, the Validator will not report the error.
* * 'level' : set to error, warning, information
*
* Entries are registered by ID, using the IDs in /org.hl7.fhir.utilities/src/main/resources/Messages.properties
*
* This feature is not supported by the validator CLI - and won't be. It's for systems hosting
* the validation framework in their own implementation context
*/
public Map<String, ValidationControl> getValidationControl() {
return validationControl;
}
}

View File

@ -491,17 +491,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return false;
}
private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message, Object... theMessageArguments) {
if (bpWarnings != null) {
switch (bpWarnings) {
case Error:
rule(errors, invalid, line, col, literalPath, test, message);
rule(errors, invalid, line, col, literalPath, test, message, theMessageArguments);
break;
case Warning:
warning(errors, invalid, line, col, literalPath, test, message);
warning(errors, invalid, line, col, literalPath, test, message, theMessageArguments);
break;
case Hint:
hint(errors, invalid, line, col, literalPath, test, message);
hint(errors, invalid, line, col, literalPath, test, message, theMessageArguments);
break;
default: // do nothing
break;
@ -2320,6 +2320,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (system != null || code != null ) {
checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, unit);
}
if (code != null && "http://unitsofmeasure.org".equals(system)) {
int b = code.indexOf("{");
int e = code.indexOf("}");
if (b >= 0 && e > 0 && b < e) {
bpCheck(theErrors, IssueType.BUSINESSRULE, element.line(), element.col(), thePath, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1));
}
}
}
private void checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {

View File

@ -36,6 +36,7 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule;
import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
@ -204,6 +205,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
} else {
val.setDebug(false);
}
if (content.has("best-practice")) {
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.valueOf(content.get("best-practice").getAsString()));
}
if (content.has("examples")) {
val.setAllowExamples(content.get("examples").getAsBoolean());
} else {
@ -367,10 +371,12 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
}
if (!TestingUtilities.context(version).isNoTerminologyServer() || !focus.has("tx-dependent")) {
Assert.assertEquals("Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("errorCount").getAsInt()) + " errors, but found " + Integer.toString(ec) + ".", java.get("errorCount").getAsInt(), ec);
if (java.has("warningCount"))
if (java.has("warningCount")) {
Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("warningCount").getAsInt()) + " warnings, but found " + Integer.toString(wc) + ".", java.get("warningCount").getAsInt(), wc);
if (java.has("infoCount"))
}
if (java.has("infoCount")) {
Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("infoCount").getAsInt()) + " hints, but found " + Integer.toString(hc) + ".", java.get("infoCount").getAsInt(), hc);
}
}
if (java.has("error-locations")) {
JsonArray el = java.getAsJsonArray("error-locations");