diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java index aa5d8b4e1..970f42308 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Runner.java @@ -25,6 +25,7 @@ import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; public class Runner implements IEvaluationContext { @@ -84,7 +85,14 @@ public class Runner implements IEvaluationContext { if (storage == null) { throw new FHIRException("No storage provided"); } - Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName()); + Validator validator = + new Validator( + context, + fpe, + prohibitedNames, + storage.supportsArrays() ? IssueSeverity.NULL : IssueSeverity.ERROR, + storage.supportsComplexTypes() ? IssueSeverity.NULL : IssueSeverity.ERROR, + storage.needsName() ? IssueSeverity.ERROR : IssueSeverity.NULL); validator.checkViewDefinition(path, viewDefinition); issues = validator.getIssues(); validator.dump(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java index 00c862b5d..8dfaf4142 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/sql/Validator.java @@ -1,7 +1,6 @@ package org.hl7.fhir.r5.utils.sql; import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -11,7 +10,6 @@ import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.fhirpath.ExpressionNode; import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; import org.hl7.fhir.r5.fhirpath.TypeDetails; -import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.model.Base64BinaryType; import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.CanonicalType; @@ -53,46 +51,57 @@ public class Validator { private FHIRPathEngine fpe; private List prohibitedNames = new ArrayList(); private List issues = new ArrayList(); - private Boolean arrays; - private Boolean complexTypes; - private Boolean needsName; + private IssueSeverity checkArrays; + private IssueSeverity checkComplexTypes; + private IssueSeverity checkNames; private String resourceName; private String name; - public Validator(IWorkerContext context, FHIRPathEngine fpe, List prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) { + /** To turn off a check, use {@code IssueSeverity.NULL}. */ + public Validator( + IWorkerContext context, + FHIRPathEngine fpe, + List prohibitedNames, + IssueSeverity checkArrays, + IssueSeverity checkComplexTypes, + IssueSeverity checkNames) { super(); this.context = context; this.fpe = fpe; this.prohibitedNames = prohibitedNames; - this.arrays = arrays; - this.complexTypes = complexTypes; - this.needsName = needsName; + this.checkArrays = checkArrays; + this.checkComplexTypes = checkComplexTypes; + this.checkNames = checkNames; } public String getResourceName() { return resourceName; } - - public void checkViewDefinition(String path, JsonObject viewDefinition) { + public void checkViewDefinition(String path, JsonObject viewDefinition) { checkProperties(viewDefinition, path, "resourceType", "url", "identifier", "name", "version", "title", "status", "experimental", "date", "publisher", "contact", "description", "useContext", "copyright", "resource", "constant", "select", "where"); JsonElement nameJ = viewDefinition.get("name"); if (nameJ == null) { - if (needsName == null) { - hint(path, viewDefinition, "No name provided. A name is required in many contexts where a ViewDefinition is used"); - } else if (needsName) { - error(path, viewDefinition, "No name provided", IssueType.REQUIRED); + if (!checkNames.isError()) { + addMessage( + checkNames, + path, + viewDefinition, + "No name provided. A name is required in many contexts where a ViewDefinition is used", + IssueType.BUSINESSRULE); + } else { + addMessage(checkNames, path, viewDefinition, "No name provided", IssueType.REQUIRED); } } else if (!(nameJ instanceof JsonString)) { - error(path, viewDefinition, "name must be a string", IssueType.INVALID); + error(path, viewDefinition, "name must be a string", IssueType.INVALID); } else { name = nameJ.asString(); - if (!isValidName(name)) { + if (!isValidName(name)) { error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT); } - if (prohibitedNames.contains(name)) { + if (prohibitedNames.contains(name)) { error(path, nameJ, "The name '"+name+"' on the viewDefinition is not allowed in this context", IssueType.BUSINESSRULE); } } @@ -279,6 +288,8 @@ public class Validator { TypeDetails td = null; ExpressionNode node = null; try { + // TODO: Add support for `fhirVersion`. + // TODO: Add support for `getReferenceKey`. node = fpe.parse(expr); column.setUserData("path", node); td = fpe.checkOnTypes(vd, resourceName, t, node, warnings); @@ -317,6 +328,7 @@ public class Validator { // ok, name is sorted! if (columnName != null) { column.setUserData("name", columnName); + // TODO: Fix this collection testing; it mis-categorizes many singletons as collections. boolean isColl = false; if (column.has("collection")) { JsonElement collectionJ = column.get("collection"); @@ -334,10 +346,24 @@ public class Validator { hint(path, column, "collection is true, but the path statement(s) can only return single values for the column '"+columnName+"'"); } } else { - if (arrays == null) { - warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path. Collections are not supported in all execution contexts"); - } else if (!arrays) { - warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path, but this is not allowed in the current execution context"); + if (!checkArrays.isError()) { + addMessage( + checkArrays, + path, + expression, + "The column '" + + columnName + + "' appears to be a collection based on it's path. Collections are not supported in all execution contexts", + IssueType.BUSINESSRULE); + } else { + addMessage( + checkArrays, + path, + expression, + "The column '" + + columnName + + "' appears to be a collection based on it's path, but this is not allowed in the current execution context", + IssueType.INVALID); } if (td.getCollectionStatus() != CollectionStatus.SINGLETON) { warning(path, column, "collection is not true, but the path statement(s) might return multiple values for the column '"+columnName+"' for some inputs"); @@ -371,17 +397,19 @@ public class Validator { error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE); } else { String type = types.iterator().next(); - boolean ok = false; + boolean ok = true; if (!isSimpleType(type) && !"null".equals(type)) { - if (complexTypes) { - warning(path, expression, "Column is a complex type. This is not supported in some Runners"); - } else if (!complexTypes) { - error(path, expression, "Column is a complex type but this is not allowed in this context", IssueType.BUSINESSRULE); - } else { - ok = true; + String message = "The column '" + + columnName + + "' is a complex type. This is not supported in some Runners"; + if (!checkComplexTypes.isError()) { + message = + "The column '" + + columnName + + "' is a complex type but this is not allowed in this context"; + ok = false; } - } else { - ok = true; + addMessage(checkComplexTypes, path, expression, message, IssueType.BUSINESSRULE); } if (ok) { Column col = new Column(columnName, isColl, type, kindForType(type)); @@ -413,7 +441,27 @@ public class Validator { } private boolean isSimpleType(String type) { - return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary", "id", "code", "date", "time"); + return Utilities.existsInList( + type, + "dateTime", + "boolean", + "integer", + "decimal", + "string", + "base64Binary", + "uri", + "url", + "canonical", + "code", + "id", + "instant", + "integer64", + "markdown", + "oid", + "positiveInt", + "time", + "unsignedInt", + "uuid"); } private String simpleType(String type) { @@ -652,34 +700,42 @@ public class Validator { } } - private void error(String path, JsonElement e, String issue, IssueType type) { - ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, type, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.ERROR); + private void addMessage(IssueSeverity severity, String path, JsonElement e, String issue, IssueType type) { + if (severity == IssueSeverity.NULL) return; + ValidationMessage vm = + new ValidationMessage( + Source.InstanceValidator, + type, + e.getStart().getLine(), + e.getStart().getCol(), + path, + issue, + severity); issues.add(vm); + } + private void error(String path, JsonElement e, String issue, IssueType type) { + addMessage(IssueSeverity.ERROR, path, e, issue, type); } private void warning(String path, JsonElement e, String issue) { - ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.WARNING); - issues.add(vm); + addMessage(IssueSeverity.WARNING, path, e, issue, IssueType.BUSINESSRULE); } private void hint(String path, JsonElement e, String issue) { - ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.INFORMATION); - issues.add(vm); + addMessage(IssueSeverity.INFORMATION, path, e, issue, IssueType.BUSINESSRULE); } public void dump() { for (ValidationMessage vm : issues) { System.out.println(vm.summary()); } - } - public void check() { + public void check() { if (!isOk()) { throw new FHIRException("View Definition is not valid"); } - } public String getName() { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 9ea21787d..6cc96e41f 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -5935,7 +5935,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if ("http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/ViewDefinition".equals(element.getProperty().getStructure().getUrl())) { if (element.getNativeObject() != null && element.getNativeObject() instanceof JsonObject) { JsonObject json = (JsonObject) element.getNativeObject(); - Validator sqlv = new Validator(context, fpe, new ArrayList<>(), null, null, null); + Validator sqlv = new Validator(context, fpe, new ArrayList<>(), IssueSeverity.WARNING, IssueSeverity.WARNING, IssueSeverity.WARNING); sqlv.checkViewDefinition(stack.getLiteralPath(), json); errors.addAll(sqlv.getIssues()); ok = sqlv.isOk() && ok;