implement contextInvariant, and finish implementing memberOf

This commit is contained in:
Grahame Grieve 2020-01-17 21:41:33 +11:00
parent d84758f54d
commit 6d87bebfe3
4 changed files with 138 additions and 79 deletions

View File

@ -2859,9 +2859,26 @@ public class FHIRPathEngine {
private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) { private List<Base> funcMemberOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
List<Base> result = new ArrayList<Base>(); List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
result.add(new BooleanType(false)); if (nl.size() != 1 || focus.size() != 1) {
return result; return new ArrayList<Base>();
}
String url = nl.get(0).primitiveValue();
ValueSet vs = hostServices != null ? hostServices.resolveValueSet(context.appInfo, url) : worker.fetchResource(ValueSet.class, url);
if (vs == null) {
return new ArrayList<Base>();
}
Base l = focus.get(0);
if (l.fhirType().equals("code")) {
return makeBoolean(worker.validateCode(terminologyServiceOptions.guessSystem(), TypeConvertor.castToCoding(l), vs).isOk());
} else if (l.fhirType().equals("Coding")) {
return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCoding(l), vs).isOk());
} else if (l.fhirType().equals("CodeableConcept")) {
return makeBoolean(worker.validateCode(terminologyServiceOptions, TypeConvertor.castToCodeableConcept(l), vs).isOk());
} else {
return new ArrayList<Base>();
}
} }

View File

@ -114,6 +114,8 @@ import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.SampledData; import org.hl7.fhir.r5.model.SampledData;
import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
@ -1491,7 +1493,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, Element resource, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, String extensionUrl) throws FHIRException { private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl) throws FHIRException {
String url = element.getNamedChildValue("url"); String url = element.getNamedChildValue("url");
boolean isModifier = element.getName().equals("modifierExtension"); boolean isModifier = element.getName().equals("modifierExtension");
@ -1541,7 +1543,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
// two questions // two questions
// 1. can this extension be used here? // 1. can this extension be used here?
checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl()); checkExtensionContext(errors, resource, container, ex, containerStack, hostContext);
if (isModifier) if (isModifier)
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
@ -1605,76 +1607,102 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return res; return res;
} }
private boolean checkExtensionContext(List<ValidationMessage> errors, Element element, StructureDefinition definition, NodeStack stack, String extensionParent) { private boolean checkExtensionContext(List<ValidationMessage> errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext) {
// String extUrl = definition.getUrl(); String extUrl = definition.getUrl();
// CommaSeparatedStringBuilder p = new CommaSeparatedStringBuilder(); boolean ok = false;
// for (String lp : stack.getLogicalPaths()) CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder();
// p.append(lp); List<String> plist = new ArrayList<>();
// if (definition.getContextType() == ExtensionContext.DATATYPE) {
// boolean ok = false; for (StructureDefinitionContextComponent ctxt : definition.getContext()) {
// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); if (ok) { break; }
// for (StringType ct : definition.getContext()) { if (ctxt.getType() == ExtensionContextType.ELEMENT) {
// b.append(ct.getValue()); if (plist.isEmpty()) {
// if (ct.getValue().equals("*") || stack.getLogicalPaths().contains(ct.getValue() + ".extension")) plist.add(stripIndexes(stack.getLiteralPath()));
// ok = true; for (String s : stack.getLogicalPaths()) {
// } plist.add(stripIndexes(s));
// return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, }
// "The extension " + extUrl + " is not allowed to be used on the logical path set [" + p.toString() + "] (allowed: datatype=" + b.toString() + ")"); }
// } else if (definition.getContextType() == ExtensionContext.EXTENSION) { String en = ctxt.getExpression();
// boolean ok = false; contexts.append("e:"+en);
// for (StringType ct : definition.getContext()) if (en.equals("Element") && !container.isResource()) {
// if (ct.getValue().equals("*") || ct.getValue().equals(extensionParent)) ok = true;
// ok = true; } else if (en.equals("Resource") && container.isResource()) {
// return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, ok = true;
// "The extension " + extUrl + " is not allowed to be used with the extension '" + extensionParent + "'"); }
// } else if (definition.getContextType() == ExtensionContext.RESOURCE) { for (String p : plist) {
// boolean ok = false; if (ok) {break; }
// // String simplePath = container.getPath(); if (p.equals(en)) {
// // System.out.println(simplePath); ok = true;
// // if (effetive.endsWith(".extension") || simplePath.endsWith(".modifierExtension")) } else {
// // simplePath = simplePath.substring(0, simplePath.lastIndexOf('.')); String pn = p;
// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); String pt = "";
// for (StringType ct : definition.getContext()) { if (p.contains(".")) {
// String c = ct.getValue(); pn = p.substring(0, p.indexOf("."));
// b.append(c); pt = p.substring(p.indexOf("."));
// if (c.equals("*") || stack.getLogicalPaths().contains(c + ".extension") || (c.startsWith("@") && stack.getLogicalPaths().contains(c.substring(1) + ".extension"))) }
// ; StructureDefinition sd = context.fetchTypeDefinition(pn);
// ok = true; while (sd != null) {
// } if ((sd.getType()+pt).equals(en)) {
// return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok, ok = true;
// "The extension " + extUrl + " is not allowed to be used on the logical path set " + p.toString() + " (allowed: resource=" + b.toString() + ")"); break;
// } else }
// throw new Error("Unsupported context type"); sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
}
}
}
} else if (ctxt.getType() == ExtensionContextType.EXTENSION) {
contexts.append("x:"+ctxt.getExpression());
NodeStack estack = stack.parent;
if (estack != null && estack.getElement().fhirType().equals("Extension")) {
String ext = estack.element.getNamedChildValue("url");
if (ctxt.getExpression().equals(ext)) {
ok = true;
}
}
} else if (ctxt.getType() == ExtensionContextType.FHIRPATH) {
contexts.append("p:"+ctxt.getExpression());
// The context is all elements that match the FHIRPath query found in the expression.
List<Base> res = fpe.evaluate(hostContext, resource, hostContext.rootResource, resource, fpe.parse(ctxt.getExpression()));
if (res.contains(container)) {
ok = true;
}
} else {
throw new Error("Unrecognised extension context "+ctxt.getTypeElement().asStringValue());
}
}
if (!rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.literalPath, ok,
"The extension " + extUrl + " is not allowed to be used at this point (allowed = "+ contexts.toString() + ")")) {
return false;
} else {
if (definition.hasContextInvariant()) {
for (StringType s : definition.getContextInvariant()) {
if (!fpe.evaluateToBoolean(hostContext, resource, hostContext.rootResource, resource, fpe.parse(s.getValue()))) {
rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.literalPath, false,
"The extension " + extUrl + " is not allowed to be used at this point (bsed on context invariant '"+s.getValue()+"')");
return false;
}
}
}
return true; return true;
} }
// }
// private String simplifyPath(String path) {
// String s = path.replace("/f:", "."); private String stripIndexes(String path) {
// while (s.contains("[")) boolean skip = false;
// s = s.substring(0, s.indexOf("["))+s.substring(s.indexOf("]")+1); StringBuilder b = new StringBuilder();
// String[] parts = s.split("\\."); for (char c : path.toCharArray()) {
// int i = 0; if (skip) {
// while (i < parts.length && !context.getProfiles().containsKey(parts[i].toLowerCase())) if (c == ']') {
// i++; skip = false;
// if (i >= parts.length) }
// throw new Error("Unable to process part "+path); } else if (c == '[') {
// int j = parts.length - 1; skip = true;
// while (j > 0 && (parts[j].equals("extension") || parts[j].equals("modifierExtension"))) } else {
// j--; b.append(c);
// StringBuilder b = new StringBuilder(); }
// boolean first = true; }
// for (int k = i; k <= j; k++) { return b.toString();
// if (k == j || !parts[k].equals(parts[k+1])) { }
// if (first)
// first = false;
// else
// b.append(".");
// b.append(parts[k]);
// }
// }
// return b.toString();
// }
//
private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent) { private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent) {
checkFixedValue(errors, path, focus, fixed, fixedSource, propName, parent, false); checkFixedValue(errors, path, focus, fixed, fixedSource, propName, parent, false);
@ -4620,7 +4648,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
thisExtension = url; thisExtension = url;
if (rule(errors, IssueType.INVALID, ei.path, !Utilities.noString(url), "Extension.url is required")) { if (rule(errors, IssueType.INVALID, ei.path, !Utilities.noString(url), "Extension.url is required")) {
if (rule(errors, IssueType.INVALID, ei.path, (extensionUrl != null) || Utilities.isAbsoluteUrl(url), "Extension.url must be an absolute URL")) { if (rule(errors, IssueType.INVALID, ei.path, (extensionUrl != null) || Utilities.isAbsoluteUrl(url), "Extension.url must be an absolute URL")) {
checkExtension(hostContext, errors, ei.path, resource, ei.element, ei.definition, profile, localStack, extensionUrl); checkExtension(hostContext, errors, ei.path, resource, element, ei.element, ei.definition, profile, localStack, stack, extensionUrl);
} }
} }
} }
@ -5379,8 +5407,10 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
} }
res.logicalPaths.add(type.getPath()); res.logicalPaths.add(type.getPath());
} else if (definition != null) { } else if (definition != null) {
for (String lp : getLogicalPaths()) for (String lp : getLogicalPaths()) {
res.logicalPaths.add(lp + "." + element.getName()); res.logicalPaths.add(lp + "." + element.getName());
}
res.logicalPaths.add(definition.typeSummary());
} else } else
res.logicalPaths.addAll(getLogicalPaths()); res.logicalPaths.addAll(getLogicalPaths());
// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); // CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
@ -5429,7 +5459,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
} }
public String reportTimes() { public String reportTimes() {
String s = String.format("Times: overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", overall, txTime, sdTime, loadTime, fpeTime); String s = String.format("Times (ms): overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", overall / 1000000, txTime / 1000000, sdTime / 1000000, loadTime / 1000000, fpeTime / 1000000);
overall = 0; overall = 0;
txTime = 0; txTime = 0;
sdTime = 0; sdTime = 0;

View File

@ -1733,7 +1733,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
public void setSnomedExtension(String sct) { public void setSnomedExtension(String sct) {
context.getExpansionParameters().addParameter("system-version", "http://snomed.info/sct|"+sct); context.getExpansionParameters().addParameter("system-version", "http://snomed.info/sct|http://snomed.info/sct/"+sct);
} }
public IValidatorResourceFetcher getFetcher() { public IValidatorResourceFetcher getFetcher() {

View File

@ -92,7 +92,8 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test @Test
public void test() throws Exception { public void test() throws Exception {
System.out.println("Name: " + name+" - base"); System.out.println("---- "+name+" ----------------------------------------------------------------");
System.out.println("** Core: ");
String txLog = null; String txLog = null;
if (content.has("txLog")) { if (content.has("txLog")) {
txLog = content.get("txLog").getAsString(); txLog = content.get("txLog").getAsString();
@ -152,6 +153,14 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
val.getContext().cacheResource(sd); val.getContext().cacheResource(sd);
} }
} }
if (content.has("valuesets")) {
for (JsonElement je : content.getAsJsonArray("valuesets")) {
String filename = je.getAsString();
String contents = TestingUtilities.loadTestResource("validator", filename);
ValueSet vs = (ValueSet) loadResource(filename, contents, v);
val.getContext().cacheResource(vs);
}
}
if (content.has("profiles")) { if (content.has("profiles")) {
for (JsonElement je : content.getAsJsonArray("profiles")) { for (JsonElement je : content.getAsJsonArray("profiles")) {
String filename = je.getAsString(); String filename = je.getAsString();
@ -170,8 +179,10 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON); val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON);
else else
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML); val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML);
System.out.println(val.reportTimes());
checkOutcomes(errors, content); checkOutcomes(errors, content);
if (content.has("profile")) { if (content.has("profile")) {
System.out.print("** Profile: ");
JsonObject profile = content.getAsJsonObject("profile"); JsonObject profile = content.getAsJsonObject("profile");
if (profile.has("supporting")) { if (profile.has("supporting")) {
for (JsonElement e : profile.getAsJsonArray("supporting")) { for (JsonElement e : profile.getAsJsonArray("supporting")) {
@ -192,6 +203,7 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd)); val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd));
else else
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML, asSdList(sd)); val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML, asSdList(sd));
System.out.println(val.reportTimes());
checkOutcomes(errorsProfile, profile); checkOutcomes(errorsProfile, profile);
} }
if (content.has("logical")) { if (content.has("logical")) {