Merge pull request #1463 from hapifhir/2023-10-gg-xig

2023 10 gg xig
This commit is contained in:
Grahame Grieve 2023-10-17 05:58:24 +11:00 committed by GitHub
commit 682352e24a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 447 additions and 211 deletions

View File

@ -155,7 +155,7 @@ public class PackageVisitor {
System.out.println("Go: "+cpidMap.size()+" current packages");
int i = 0;
for (String s : cpidMap.keySet()) {
processCurrentPackage(s, cpidMap.get(s), cpidSet, i, cpidMap.size());
processCurrentPackage(cpidMap.get(s), s, cpidSet, i, cpidMap.size());
i++;
}
@ -266,12 +266,20 @@ public class PackageVisitor {
}
private Map<String, String> getAllCIPackages() throws IOException {
System.out.println("Fetch https://build.fhir.org/ig/qas.json");
Map<String, String> res = new HashMap<>();
if (current) {
JsonArray json = (JsonArray) JsonParser.parseFromUrl("https://build.fhir.org/ig/qas.json");
for (JsonObject o : json.asJsonObjects()) {
String url = o.asString("repo");
res.put(url, o.asString("package-id"));
String pid = o.asString("package-id");
if (url.contains("/branches/master") || url.contains("/branches/main") ) {
if (!res.containsKey(pid)) {
res.put(pid, url);
} else if (!url.equals(res.get(pid))) {
System.out.println("Ignore "+url+" already encountered "+pid +" @ "+res.get(pid));
}
}
}
}
return res;

View File

@ -308,4 +308,9 @@ public class ComparisonRenderer implements IEvaluationContext {
return null;
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}

View File

@ -132,6 +132,7 @@ public class Element extends Base implements NamedItem {
private Base source;
private boolean ignorePropertyOrder;
private FhirFormat format;
private Object nativeObject;
public Element(String name) {
super();
@ -1478,4 +1479,13 @@ public class Element extends Base implements NamedItem {
return this;
}
public Object getNativeObject() {
return nativeObject;
}
public Element setNativeObject(Object nativeObject) {
this.nativeObject = nativeObject;
return this;
}
}

View File

@ -193,6 +193,7 @@ public class JsonParser extends ParserBase {
}
private void checkObject(List<ValidationMessage> errors, JsonObject object, Element b, String path) {
b.setNativeObject(object);
checkComments(errors, object, b, path);
if (policy == ValidationPolicy.EVERYTHING) {
if (object.getProperties().size() == 0) {

View File

@ -224,6 +224,12 @@ public abstract class ParserBase {
new ContextUtilities(context).generateSnapshot(sd);
return sd;
}
}
for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
if (name.equals(sd.getUrl()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
new ContextUtilities(context).generateSnapshot(sd);
return sd;
}
}
logError(errors, ValidationMessage.NO_RULE_DATE, line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);
return null;

View File

@ -302,7 +302,7 @@ public class XmlParser extends ParserBase {
public Element parse(List<ValidationMessage> errors, org.w3c.dom.Element base, String type) throws Exception {
StructureDefinition sd = getDefinition(errors, 0, 0, FormatUtilities.FHIR_NS, type);
Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)).setFormat(FhirFormat.XML);
Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)).setFormat(FhirFormat.XML).setNativeObject(base);
result.setPath(base.getLocalName());
String path = "/"+pathPrefix(base.getNamespaceURI())+base.getLocalName();
checkElement(errors, base, path, result.getProperty(), false);
@ -431,7 +431,7 @@ public class XmlParser extends ParserBase {
}
}
}
Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML);
Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n);
} else {
@ -440,7 +440,7 @@ public class XmlParser extends ParserBase {
if (!property.isChoice() && !name.equals(property.getName())) {
name = property.getName();
}
Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML);
Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
if (property.isList()) {
n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]");
} else {
@ -497,7 +497,7 @@ public class XmlParser extends ParserBase {
npath = npath+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName();
name = child.getLocalName();
Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML);
Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
cgn.getChildren().add(n);
n.setPath(element.getPath()+"."+property.getName());
checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty(), false);
@ -524,7 +524,7 @@ public class XmlParser extends ParserBase {
npath = npath+"/text()";
name = mtProp.getName();
Element n = new Element(name, mtProp, mtProp.getType(), child.getTextContent().trim()).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML);
Element n = new Element(name, mtProp, mtProp.getType(), child.getTextContent().trim()).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child);
cgn.getChildren().add(n);
n.setPath(element.getPath()+"."+mtProp.getName());

View File

@ -1518,6 +1518,15 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
c.addPiece(gen.new Piece(null, "Instances of this logical model cannot be the target of a Reference", null).addStyle("font-weight:bold"));
}
String ps = ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_PROFILE_STYLE);
if (ps != null) {
if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
if ("cda".equals(ps)) {
c.addPiece(gen.new Piece(null, "Instances of this type are validated by templateId", null).addStyle("font-weight:bold"));
} else {
c.addPiece(gen.new Piece(null, "Instances of this type are validated using an unknown approach: "+ps, null).addStyle("font-weight:bold"));
}
}
}
}
if (definition != null) {
@ -3564,6 +3573,14 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
} else {
tableRow(tbl, "Logical Model", null, strikethrough, "Instances of this logical model cannot be the target of a Reference");
}
String ps = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_PROFILE_STYLE);
if (ps != null) {
if ("cda".equals(ps)) {
tableRow(tbl, "Validation", null, strikethrough, "Instances of this type are validated by templateId");
} else {
tableRow(tbl, "Validation", null, strikethrough, "Instances of this type are validated using an unknown approach: "+ps);
}
}
}
if (root && sd.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {

View File

@ -9,7 +9,7 @@ public class URICodeSystem extends SpecialCodeSystem {
@Override
public ConceptDefinitionComponent findConcept(Coding code) {
if (Utilities.isAbsoluteUrl(code.getCode())) {
return new ConceptDefinitionComponent(code.getCode());
return new ConceptDefinitionComponent(code.getCode()).setDisplay(code.hasDisplay() ? code.getDisplay() : code.getCode());
} else {
return null;
}

View File

@ -1265,7 +1265,9 @@ public class ValueSetValidator extends ValueSetProcessBase {
public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list, AlternateCodesProcessingRules altCodeRules) {
opContext.deadCheck();
if (def.getCaseSensitive()) {
if (def.hasUserData("tx.cs.special")) {
return ((SpecialCodeSystem) def.getUserData("tx.cs.special")).findConcept(new Coding().setCode(code)) != null;
} else if (def.getCaseSensitive()) {
for (ConceptDefinitionComponent cc : list) {
if (cc.getCode().equals(code)) {
return true;

View File

@ -33,6 +33,7 @@ import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.BaseDateTimeType;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.DateTimeType;
@ -203,6 +204,13 @@ public class FHIRPathEngine {
* return the value set referenced by the url, which has been used in memberOf()
*/
public ValueSet resolveValueSet(Object appContext, String url);
/**
* For the moment, there can only be one parameter if it's a type parameter
* @param name
* @return true if it's a type parameter
*/
public boolean paramIsType(String name, int index);
}
/**
@ -3162,7 +3170,7 @@ public class FHIRPathEngine {
@SuppressWarnings("unchecked")
private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, Set<ElementDefinition> elementDependencies, ExpressionNode container) throws PathEngineException, DefinitionException {
List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) {
if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType || (exp.getFunction() == Function.Custom && hostServices.paramIsType(exp.getName(), 0))) {
paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
} else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) {
TypeDetails base = TypeDetails.empty();
@ -3793,8 +3801,22 @@ public class FHIRPathEngine {
case Custom: {
List<List<Base>> params = new ArrayList<List<Base>>();
for (ExpressionNode p : exp.getParameters()) {
params.add(execute(context, focus, p, true));
if (hostServices.paramIsType( exp.getName(), 0)) {
if (exp.getParameters().size() > 0) {
String tn;
if (exp.getParameters().get(0).getInner() != null) {
tn = exp.getParameters().get(0).getName()+"."+exp.getParameters().get(0).getInner().getName();
} else {
tn = "FHIR."+exp.getParameters().get(0).getName();
}
List<Base> p = new ArrayList<>();
p.add(new CodeType(tn));
params.add(p);
}
} else {
for (ExpressionNode p : exp.getParameters()) {
params.add(execute(context, focus, p, true));
}
}
return hostServices.executeFunction(context.appInfo, focus, exp.getName(), params);
}

View File

@ -853,4 +853,8 @@ public class LiquidEngine implements IEvaluationContext {
return replaced;
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}

View File

@ -257,6 +257,7 @@ public class ToolingExtensions {
public static final String EXT_NO_BINDING = "http://hl7.org/fhir/tools/StructureDefinition/no-binding";
public static final String EXT_ID_CHOICE_GROUP = "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group";
public static final String EXT_DATE_RULES = "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-rules";
public static final String EXT_PROFILE_STYLE = "http://hl7.org/fhir/tools/StructureDefinition/type-profile-style";
// specific extension helpers

View File

@ -111,7 +111,11 @@ public class Runner implements IEvaluationContext {
}
if (ok) {
List<List<Cell>> rows = new ArrayList<>();
generateCells(b, vd, rows);
rows.add(new ArrayList<Cell>());
for (JsonObject select : vd.getJsonObjects("select")) {
executeSelect(select, b, rows);
}
for (List<Cell> row : rows) {
storage.addRow(store, row);
}
@ -119,48 +123,47 @@ public class Runner implements IEvaluationContext {
}
storage.finish(store);
}
private void generateCells(Base bl, JsonObject vd, List<List<Cell>> rows) {
if (vd.has("forEach")) {
executeForEach(vd, bl, rows);
} else if (vd.has("forEachOrNull")) {
executeForEachOrNull(vd, bl, rows);
} else if (vd.has("union")) {
executeUnion(vd, bl, rows);
} else {
for (JsonObject select : vd.getJsonObjects("select")) {
executeSelect(select, bl, rows);
}
}
}
private void executeSelect(JsonObject select, Base bl, List<List<Cell>> rows) {
if (select.has("path")) {
executeSelectPath(select, bl, rows);
} else if (select.has("forEach")) {
executeForEach(select, bl, rows);
private void executeSelect(JsonObject select, Base b, List<List<Cell>> rows) {
List<Base> focus = new ArrayList<>();
if (select.has("forEach")) {
focus.addAll(executeForEach(select, b));
} else if (select.has("forEachOrNull")) {
executeForEachOrNull(select, bl, rows);
} else if (select.has("union")) {
executeUnion(select, bl, rows);
focus.addAll(executeForEachOrNull(select, b));
} else {
focus.add(b);
}
}
private void executeForEach(JsonObject focus, Base b, List<List<Cell>> rows) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEach");
List<Base> bl2 = fpe.evaluate(b, n);
// } else if (select.has("union")) {
// focus.addAll(executeUnion(select, b));
List<List<Cell>> tempRows = new ArrayList<>();
tempRows.addAll(rows);
rows.clear();
for (Base b2 : bl2) {
List<List<Cell>> rowsToAdd = cloneRows(tempRows);
for (JsonObject select : focus.getJsonObjects("select")) {
executeSelect(select, b2, rowsToAdd);
for (Base f : focus) {
List<List<Cell>> rowsToAdd = cloneRows(tempRows);
for (JsonObject column : select.getJsonObjects("column")) {
executeColumn(column, f, rowsToAdd);
}
for (JsonObject sub : select.getJsonObjects("union")) {
executeSelect(sub, f, rowsToAdd);
}
for (JsonObject sub : select.getJsonObjects("select")) {
executeSelect(sub, f, rowsToAdd);
}
rows.addAll(rowsToAdd);
}
}
private List<Base> executeUnion(JsonObject focus, Base b, List<List<Cell>> rows) {
throw new FHIRException("union is not supported");
}
private List<List<Cell>> cloneRows(List<List<Cell>> rows) {
List<List<Cell>> list = new ArrayList<>();
for (List<Cell> row : rows) {
@ -176,30 +179,38 @@ public class Runner implements IEvaluationContext {
}
return list;
}
private void executeForEachOrNull(JsonObject focus, Base b, List<List<Cell>> rows) {
throw new FHIRException("forEachOrNull is not supported");
private List<Base> executeForEach(JsonObject focus, Base b) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEach");
List<Base> result = new ArrayList<>();
result.addAll(fpe.evaluate(b, n));
return result;
}
private void executeUnion(JsonObject focus, Base b, List<List<Cell>> rows) {
throw new FHIRException("union is not supported");
private List<Base> executeForEachOrNull(JsonObject focus, Base b) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull");
List<Base> result = new ArrayList<>();
result.addAll(fpe.evaluate(b, n));
if (result.size() == 0) {
result.add(null);
}
return result;
}
private void executeSelectPath(JsonObject select, Base b, List<List<Cell>> rows) {
ExpressionNode n = (ExpressionNode) select.getUserData("path");
List<Base> bl2 = fpe.evaluate(b, n);
String name = select.getUserString("name");
if (!bl2.isEmpty()) {
if (rows.isEmpty()) {
rows.add(new ArrayList<Cell>());
}
for (List<Cell> row : rows) {
Cell c = cell(row, name);
if (c == null) {
c = new Cell(column(name));
row.add(c);
}
private void executeColumn(JsonObject column, Base b, List<List<Cell>> rows) {
ExpressionNode n = (ExpressionNode) column.getUserData("path");
List<Base> bl2 = new ArrayList<>();
if (b != null) {
bl2.addAll(fpe.evaluate(b, n));
}
String name = column.getUserString("name");
for (List<Cell> row : rows) {
Cell c = cell(row, name);
if (c == null) {
c = new Cell(column(name));
row.add(c);
}
if (!bl2.isEmpty()) {
if (bl2.size() + c.getValues().size() > 1) {
// this is a problem if collection != true or if the storage can't deal with it
// though this should've been picked up before now - but there are circumstances where it wouldn't be
@ -211,14 +222,6 @@ public class Runner implements IEvaluationContext {
c.getValues().add(genValue(c.getColumn(), b2));
}
}
} else {
for (List<Cell> row : rows) {
Cell c = cell(row, name);
if (c == null) {
c = new Cell(column(name));
row.add(c);
}
}
}
}
@ -376,6 +379,9 @@ public class Runner implements IEvaluationContext {
String rt = null;
if (parameters.size() > 0) {
rt = parameters.get(0).get(0).primitiveValue();
if (rt.startsWith("FHIR.")) {
rt = rt.substring(5);
}
}
List<Base> base = new ArrayList<Base>();
if (focus.size() == 1) {
@ -427,6 +433,10 @@ public class Runner implements IEvaluationContext {
public ValueSet resolveValueSet(Object appContext, String url) {
throw new Error("Not implemented yet: resolveValueSet");
}
@Override
public boolean paramIsType(String name, int index) {
return "getReferenceKey".equals(name);
}
}

View File

@ -1,6 +1,7 @@
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;
@ -24,20 +25,20 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public class Validator {
private IWorkerContext context;
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 Boolean arrays;
private Boolean complexTypes;
private Boolean needsName;
private String resourceName;
private List<Column> columns = new ArrayList<Column>();
private String name;
protected Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, boolean arrays, boolean complexTypes, boolean needsName) {
public Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) {
super();
this.context = context;
this.fpe = fpe;
@ -58,7 +59,9 @@ public class Validator {
public void checkViewDefinition(String path, JsonObject viewDefinition) {
JsonElement nameJ = viewDefinition.get("name");
if (nameJ == null) {
if (needsName) {
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);
}
} else if (!(nameJ instanceof JsonString)) {
@ -122,119 +125,201 @@ public class Validator {
}
private void checkSelect(String path, JsonObject select, TypeDetails t) {
if (select.has("path")) {
checkSelectPath(path, select, select.get("path"), t);
} else if (select.has("forEach")) {
checkForEach(path, select, select.get("forEach"), t);
if (select.has("forEach")) {
t = checkForEach(path, select, select.get("forEach"), t);
} else if (select.has("forEachOrNull")) {
checkForEachOrNull(path, select, select.get("forEachOrNull"), t);
} else if (select.has("union")) {
checkUnion(path, select, select.get("union"), t);
} else {
error(path, select, "The select has neither a path, forEach, forEachOrNull, or union statement", IssueType.REQUIRED);
t = checkForEachOrNull(path, select, select.get("forEachOrNull"), t);
}
if (t != null) {
boolean content = false;
if (select.has("union")) {
content = checkUnion(path, select, select.get("union"), t);
}
if (select.has("column")) {
JsonElement a = select.get("column");
if (!(a instanceof JsonArray)) {
error(path+".column", a, "column is not an array", IssueType.INVALID);
} else {
content = true;
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID);
} else {
checkColumn(path+".column["+i+"]", (JsonObject) e, t);
}
}
}
}
if (select.has("select")) {
JsonElement a = select.get("select");
if (!(a instanceof JsonArray)) {
error(path+".select", a, "select is not an array", IssueType.INVALID);
} else {
content = true;
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID);
} else {
checkSelect(path+".select["+i+"]", (JsonObject) e, t);
}
}
}
}
if (!content) {
error(path, select, "The select has no columns or selects", IssueType.REQUIRED);
}
}
}
private void checkSelectPath(String path, JsonObject select, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
} else {
String expr = expression.asString();
List<String> warnings = new ArrayList<>();
TypeDetails td = null;
ExpressionNode node = null;
try {
node = fpe.parse(expr);
select.setUserData("path", node);
td = fpe.checkOnTypes(null, resourceName, t, node, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
private boolean checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
JsonElement a = focus.get("union");
if (!(a instanceof JsonArray)) {
error(path+".union", a, "union is not an array", IssueType.INVALID);
return false;
} else {
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".union["+i+"]", e, "union["+i+"] is not an object", IssueType.INVALID);
} else {
checkSelect(path+".union["+i+"]", (JsonObject) e, t);
}
}
if (i < 2) {
warning(path+".union", a, "union should have more than one item");
}
if (td != null && node != null) {
for (String s : warnings) {
warning(path+".path", expression, s);
}
String columnName = null;
JsonElement aliasJ = select.get("alias");
if (aliasJ != null) {
if (aliasJ instanceof JsonString) {
columnName = aliasJ.asString();
if (!isValidName(columnName)) {
error(path+".name", aliasJ, "The name '"+columnName+"' is not valid", IssueType.VALUE);
}
} else {
error(path+".alias", aliasJ, "alias must be a string", IssueType.INVALID);
}
}
if (columnName == null) {
List<String> names = node.getDistalNames();
if (names.size() == 1 && names.get(0) != null) {
columnName = names.get(0);
if (!isValidName(columnName)) {
error(path+".path", expression, "The name '"+columnName+"' found in the path expression is not a valid column name, so an alias is required", IssueType.INVARIANT);
}
} else {
error(path, select, "The path does not resolve to a name, so an alias is required", IssueType.REQUIRED);
}
}
// ok, name is sorted!
if (columnName != null) {
select.setUserData("name", columnName);
boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON) || column(columnName) != null;
if (select.has("collection")) {
JsonElement collectionJ = select.get("collection");
if (!(collectionJ instanceof JsonBoolean)) {
error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID);
} else {
boolean collection = collectionJ.asJsonBoolean().asBoolean();
if (!collection && isColl) {
isColl = false;
warning(path, select, "collection is false, but the path statement(s) might return multiple values for the column '"+columnName+"' some inputs");
}
}
}
if (isColl && !arrays) {
warning(path, expression, "column appears to be a collection, but this is not allowed in this context");
}
// ok collection is sorted
Set<String> types = new HashSet<>();
for (String type : td.getTypes()) {
types.add(simpleType(type));
}
return true;
}
}
private void checkColumn(String path, JsonObject column, TypeDetails t) {
if (!column.has("path")) {
error(path, column, "no path found", IssueType.INVALID);
} else {
JsonElement expression = column.get("path");
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
} else {
String expr = expression.asString();
JsonElement typeJ = select.get("type");
if (typeJ != null) {
if (typeJ instanceof JsonString) {
String type = typeJ.asString();
if (!td.hasType(type)) {
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type, IssueType.VALUE);
} else {
types.clear();
types.add(simpleType(type));
List<String> warnings = new ArrayList<>();
TypeDetails td = null;
ExpressionNode node = null;
try {
node = fpe.parse(expr);
column.setUserData("path", node);
td = fpe.checkOnTypes(null, resourceName, t, node, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null && node != null) {
for (String s : warnings) {
warning(path+".path", expression, s);
}
String columnName = null;
JsonElement aliasJ = column.get("alias");
if (aliasJ != null) {
if (aliasJ instanceof JsonString) {
columnName = aliasJ.asString();
if (!isValidName(columnName)) {
error(path+".name", aliasJ, "The name '"+columnName+"' is not valid", IssueType.VALUE);
}
} else {
error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
error(path+".alias", aliasJ, "alias must be a string", IssueType.INVALID);
}
}
if (types.size() != 1) {
error(path, select, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE);
} else {
String type = types.iterator().next();
if (!isSimpleType(type) && !complexTypes) {
error(path, expression, "column is a complex type but this is not allowed in this context", IssueType.BUSINESSRULE);
if (columnName == null) {
List<String> names = node.getDistalNames();
if (names.size() == 1 && names.get(0) != null) {
columnName = names.get(0);
if (!isValidName(columnName)) {
error(path+".path", expression, "The name '"+columnName+"' found in the path expression is not a valid column name, so an alias is required", IssueType.INVARIANT);
}
} else {
Column col = column(columnName);
if (col != null) {
if (!col.getType().equals(type)) {
error(path, expression, "Duplicate definition for "+columnName+" has different types ("+col.getType()+" vs "+type+")", IssueType.BUSINESSRULE);
error(path, column, "The path does not resolve to a name, so an alias is required", IssueType.REQUIRED);
}
}
// ok, name is sorted!
if (columnName != null) {
column.setUserData("name", columnName);
boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON) || column(columnName) != null;
if (column.has("collection")) {
JsonElement collectionJ = column.get("collection");
if (!(collectionJ instanceof JsonBoolean)) {
error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID);
} else {
boolean collection = collectionJ.asJsonBoolean().asBoolean();
if (!collection && isColl) {
isColl = false;
warning(path, column, "collection is false, but the path statement(s) might return multiple values for the column '"+columnName+"' some inputs");
}
if (col.isColl() != isColl) {
error(path, expression, "Duplicate definition for "+columnName+" has different status for collection ("+col.isColl()+" vs "+isColl+")", IssueType.BUSINESSRULE);
}
}
if (isColl) {
if (arrays == null) {
warning(path, expression, "column appears to be a collection. Collections are not supported in all Runners");
} else if (!arrays) {
warning(path, expression, "column appears to be a collection, but this is not allowed in this context");
}
}
// ok collection is sorted
Set<String> types = new HashSet<>();
for (String type : td.getTypes()) {
types.add(simpleType(type));
}
JsonElement typeJ = column.get("type");
if (typeJ != null) {
if (typeJ instanceof JsonString) {
String type = typeJ.asString();
if (!td.hasType(type)) {
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type, IssueType.VALUE);
} else {
types.clear();
types.add(simpleType(type));
}
} else {
columns.add(new Column(columnName, isColl, type, kindForType(type)));
error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
}
}
if (types.size() != 1) {
error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE);
} else {
String type = types.iterator().next();
boolean ok = false;
if (!isSimpleType(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;
}
} else {
ok = true;
}
if (ok) {
Column col = column(columnName);
if (col != null) {
if (!col.getType().equals(type)) {
error(path, expression, "Duplicate definition for "+columnName+" has different types ("+col.getType()+" vs "+type+")", IssueType.BUSINESSRULE);
}
if (col.isColl() != isColl) {
error(path, expression, "Duplicate definition for "+columnName+" has different status for collection ("+col.isColl()+" vs "+isColl+")", IssueType.BUSINESSRULE);
}
} else {
columns.add(new Column(columnName, isColl, type, kindForType(type)));
}
}
}
}
@ -295,9 +380,10 @@ public class Validator {
return type;
}
private void checkForEach(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
private TypeDetails checkForEach(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
return null;
} else {
String expr = expression.asString();
@ -312,28 +398,36 @@ public class Validator {
}
if (td != null) {
for (String s : warnings) {
warning(path+".path", expression, s);
}
int i = 0;
if (checkAllObjects(path, focus, "select")) {
for (JsonObject select : focus.getJsonObjects("select")) {
checkSelect(path+".select["+i+"]", select, td);
i++;
}
if (i == 0) {
error(path, focus, "No select statements found", IssueType.REQUIRED);
}
warning(path+".forEach", expression, s);
}
}
return td;
}
}
private void checkForEachOrNull(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
error(path+".forEachOrNull", expression, "forEachOrNull is not supported", IssueType.BUSINESSRULE);
}
private TypeDetails checkForEachOrNull(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEachOrNull", expression, "forEachOrNull is not a string", IssueType.INVALID);
return null;
} else {
String expr = expression.asString();
private void checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
error(path+".union", focus.get("union"), "union is not supported", IssueType.BUSINESSRULE);
List<String> warnings = new ArrayList<>();
TypeDetails td = null;
try {
ExpressionNode n = fpe.parse(expr);
focus.setUserData("forEachOrNull", n);
td = fpe.checkOnTypes(null, resourceName, t, n, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null) {
for (String s : warnings) {
warning(path+".forEachOrNull", expression, s);
}
}
return td;
}
}
private void checkConstant(String path, JsonObject constant) {
@ -481,27 +575,40 @@ public class Validator {
issues.add(vm);
}
public void dump() {
for (ValidationMessage vm : issues) {
System.out.println(vm.summary());
}
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);
}
public void check() {
public void dump() {
for (ValidationMessage vm : issues) {
System.out.println(vm.summary());
}
}
public void check() {
if (!isOk()) {
throw new FHIRException("View Definition is not valid");
}
}
public String getName() {
return name;
}
public List<ValidationMessage> getIssues() {
return issues;
}
public boolean isOk() {
boolean ok = true;
for (ValidationMessage vm : issues) {
if (vm.isError()) {
ok = false;
}
}
if (!ok) {
throw new FHIRException("View Definition is not valid");
}
}
public String getName() {
return name;
return ok;
}
}

View File

@ -100,4 +100,8 @@ public class FHIRPathHostServices implements FHIRPathEngine.IEvaluationContext {
return structureMapUtilities.getWorker().fetchResource(ValueSet.class, url);
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}

View File

@ -95,6 +95,10 @@ public class FHIRPathTests {
return TestingUtilities.getSharedWorkerContext().fetchResource(ValueSet.class, url);
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private static FHIRPathEngine fp;

View File

@ -420,6 +420,10 @@ public class SnapShotGenerationTests {
throw new Error("Not implemented yet");
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private static FHIRPathEngine fp;

View File

@ -232,7 +232,7 @@ Type_Specific_Checks_DT_URL_Resolve = URL value ''{0}'' does not resolve
Type_Specific_Checks_DT_UUID_Strat = UUIDs must start with urn:uuid:
Type_Specific_Checks_DT_UUID_Valid = UUIDs must be valid and lowercase ({0})
Validation_BUNDLE_Message = The first entry in a message must be a MessageHeader
Validation_VAL_Content_Unknown = Unrecognised Content {0}
Validation_VAL_Content_Unknown = Unrecognized Content {0}
Validation_VAL_NoType = Unknown type {0}
Validation_VAL_Profile_MatchMultiple = Profile {0}, Element matches more than one slice - {1}, {2}
## for the next 4 messages, the available parameters are: 0: profile url, 1: ed.path, 2: ed.id, 3: ed.sliceName, 4: ed.label, 5: element.path, 6: ed.min and optionally 7: actual count
@ -305,7 +305,7 @@ No_reference_resolving_discriminator__from_ = No reference resolving discriminat
Unable_to_resolve_element__in_profile_ = Unable to resolve element {0} in profile {1}
Unable_to_resolve_profile_ = Unable to resolve profile {0}
Resource_resolution_services_not_provided = Resource resolution services not provided
Unrecognised_extension_context_ = Unrecognised extension context {0}
Unrecognised_extension_context_ = Unrecognized extension context {0}
Unable_to_locate_the_profile__in_order_to_validate_against_it = Unable to locate the profile ''{0}'' in order to validate against it
Reference__refers_to_a__not_a_ValueSet = Reference {0} refers to a {1} not a ValueSet
Not_done_yet_ValidatorHostServicesconformsToProfile_when_item_is_not_an_element = Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element
@ -394,9 +394,9 @@ Does_not_match_slice_ = Does not match slice ''{0}'' (discriminator: {1})
Profile__does_not_match_for__because_of_the_following_profile_issues__ = Profile {0} does not match for {1} because of the following profile issues: {2}
This_element_does_not_match_any_known_slice_ = This element does not match any known slice {0}
defined_in_the_profile = Defined in the profile
This_does_not_appear_to_be_a_FHIR_resource_unknown_name_ = This content cannot be parsed (unknown or unrecognised XML root element name ''{0}'')
This_does_not_appear_to_be_a_FHIR_resource_unknown_name_ = This content cannot be parsed (unknown or unrecognized resource name ''{0}'')
This_cannot_be_parsed_as_a_FHIR_object_no_name = This content cannot be parsed (no name)
This_does_not_appear_to_be_a_FHIR_resource_unknown_namespacename_ = This content cannot be parsed (unknown or unrecognised XML Root element namespace/name ''{0}::{1}'')
This_does_not_appear_to_be_a_FHIR_resource_unknown_namespacename_ = This content cannot be parsed (unknown or unrecognized XML Root element namespace/name ''{0}::{1}'')
This__cannot_be_parsed_as_a_FHIR_object_no_namespace = This ''{0}'' cannot be parsed (no namespace on the XML Root element)
Unable_to_find_resourceType_property = Unable to find resourceType property
Error_parsing_JSON_the_primitive_value_must_be_a_string = Error parsing JSON: the primitive value must be a string
@ -427,7 +427,7 @@ Unknown_resource_type_missing_rdfstype = Unknown resource type (missing rdfs:typ
reference_to__cannot_be_resolved = reference to {0} cannot be resolved
This_property_must_be_a_URI_or_bnode_not_ = This property must be a URI or bnode, not {0}
This_property_must_be_a_Literal_not_ = This property must be a Literal, not {0}
Unrecognised_predicate_ = Unrecognised predicate ''{0}''
Unrecognised_predicate_ = Unrecognized predicate ''{0}''
Error_parsing_Turtle_ = Error parsing Turtle: {0}
Unexpected_datatype_for_rdfstype = Unexpected datatype for rdfs:type
Attempt_to_replace_element_name_for_a_nonchoice_type=Attempt to replace element name for a non-choice type

View File

@ -337,6 +337,11 @@ public class ValidatorCli {
res.add("5.0");
res.add("-ig");
res.add("hl7.cda.uv.core#2.1.0-draft1");
} else if (a.equals("-view-definition")) {
res.add("-version");
res.add("5.0");
res.add("-ig");
res.add("hl7.fhir.uv.sql-on-fhir#current");
} else {
res.add(a);
}

View File

@ -160,6 +160,7 @@ import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.TypedElementDefinition;
import org.hl7.fhir.r5.utils.ResourceUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.sql.Validator;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
@ -429,6 +430,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return context.fetchResource(ValueSet.class, url);
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private FHIRPathEngine fpe;
@ -5379,6 +5385,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return new StructureMapValidator(this, fpe, profileUtilities).validateStructureMap(errors, element, stack) && ok;
} else if (element.getType().equals("ValueSet")) {
return new ValueSetValidator(this).validateValueSet(errors, element, stack) && ok;
} 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);
sqlv.checkViewDefinition(stack.getLiteralPath(), json);
errors.addAll(sqlv.getIssues());
ok = sqlv.isOk() && ok;
}
return ok;
} else {
return ok;
}

View File

@ -397,6 +397,10 @@ public class SnapShotGenerationXTests {
throw new Error("Not implemented yet");
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private static FHIRPathEngine fp;
@ -577,4 +581,6 @@ public class SnapShotGenerationXTests {
}
return sd;
}
}

View File

@ -804,4 +804,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}

View File

@ -20,7 +20,7 @@
<properties>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.4.10</validator_test_case_version>
<validator_test_case_version>1.4.11-SNAPSHOT</validator_test_case_version>
<jackson_version>2.15.2</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>