This commit is contained in:
Bashir Sadjad 2024-09-25 17:46:13 -04:00 committed by GitHub
commit 50d19800f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 107 additions and 43 deletions

View File

@ -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();

View File

@ -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<String> prohibitedNames = new ArrayList<String>();
private List<ValidationMessage> issues = new ArrayList<ValidationMessage>();
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<String> prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) {
/** To turn off a check, use {@code IssueSeverity.NULL}. */
public Validator(
IWorkerContext context,
FHIRPathEngine fpe,
List<String> 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() {

View File

@ -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;