Add check for UCUM annotations + add ValidationControl for hosting the validator in external processes
This commit is contained in:
parent
f7b0e03036
commit
b47a1da054
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue