Sync DSTU2 validator with RI

This commit is contained in:
jamesagnew 2016-05-24 09:22:16 -04:00
parent efe9cd1dd1
commit 35ffbf1568
12 changed files with 7201 additions and 222 deletions

View File

@ -183,6 +183,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
myDaoConfig.setHardSearchLimit(1000); myDaoConfig.setHardSearchLimit(1000);
myDaoConfig.setHardTagListLimit(1000); myDaoConfig.setHardTagListLimit(1000);
myDaoConfig.setIncludeLimit(2000); myDaoConfig.setIncludeLimit(2000);
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
} }
@Before @Before

View File

@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
@ -41,6 +42,7 @@ import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
@ -2186,6 +2188,53 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
} }
} }
/**
* From example from david hay - moved to the hl7org_dstu2 project
*/
@Test
@Ignore
public void testValidateDavidsAllergyIntolerance() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
/*
* Upload structurredef
*/
String contents = IOUtils.toString(getClass().getResourceAsStream("/allergyintolerance-sd-david.json"), "UTF-8");
HttpEntityEnclosingRequestBase post = new HttpPut(ourServerBase + "/StructureDefinition/ohAllergyIntolerance");
post.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertEquals(201, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
/*
* Validate
*/
contents = IOUtils.toString(getClass().getResourceAsStream("/allergyintolerance-david.json"), "UTF-8");
post = new HttpPost(ourServerBase + "/AllergyIntolerance/$validate?_pretty=true");
post.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
response = ourHttpClient.execute(post);
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertEquals(200, response.getStatusLine().getStatusCode());
assertThat(resp, not(containsString("Resource has no id")));
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
}
@Test @Test
public void testValidateResourceHuge() throws IOException { public void testValidateResourceHuge() throws IOException {
@ -2318,4 +2367,5 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
} }
} }

View File

@ -0,0 +1,152 @@
{
"resourceType": "AllergyIntolerance",
"id": "84651",
"meta": {
"versionId": "1",
"lastUpdated": "2016-05-19T15:43:49.815-04:00",
"profile": [
"http://fhir.hl7.org.nz/dstu2/StructureDefinition/ohAllergyIntolerance"
]
},
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-endDate",
"valueDateTime": "2016-05-03"
},
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-informantRole",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "133932002",
"display": "Carer"
}
]
}
},
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-reasonClosed",
"valueCodeableConcept": {
"coding": [
{
"system": "http://orionhealth.com/problem-list/adverse-reaction-reason-closed",
"code": "1",
"display": "Disproven"
}
]
}
},
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-classification",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "419076005",
"display": "Allergic reaction"
}
]
}
}
],
"language": [
"en"
],
"identifier": [
{
"system": "http://orionhealth.com/identifier/problem-list/ORGNISATION_ORION",
"value": "2.25.148135789146703435349240718177383549558.8.4.4.0/522911"
}
],
"patient": {
"reference": "Patient/64909"
},
"substance": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "84430008",
"display": "Cross reacting antibody"
}
]
},
"status": "inactive",
"criticality": "CRITH",
"category": "environment",
"reaction": [
{
"certainty": "likely",
"_certainty": {
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/originalValue",
"valueCodeableConcept": {
"coding": [
{
"system": "http://orionhealth.com/problem-list/adverse-reaction-certainty",
"code": "SUSPECTED",
"display": "Suspected"
}
]
}
}
]
},
"manifestation": [
{
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-manifestationSeverity",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/900000000000207008/version/20150531",
"code": "442452003",
"display": "Life-Threatening"
}
]
}
}
],
"coding": [
{
"system": "http://orionhealth.com/problem-list/clinical-manifestation",
"code": "FT",
"display": "rlo"
}
]
},
{
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-manifestationSeverity",
"valueCodeableConcept": {
"coding": [
{
"system": "http://orionhealth.com/problem-list/severity",
"code": "UNKNOWN",
"display": "Unknown"
}
]
}
}
],
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "95332009",
"display": "Rash of systemic lupus erythematosus"
}
]
}
],
"severity": "severe"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,6 @@ public class FhirInstanceValidatorDstu3Test {
myValidConcepts.add(theSystem + "___" + theCode); myValidConcepts.add(theSystem + "___" + theCode);
} }
@Test @Test
// @Ignore // @Ignore
public void testValidateBuiltInProfiles() throws Exception { public void testValidateBuiltInProfiles() throws Exception {
@ -142,11 +141,11 @@ public class FhirInstanceValidatorDstu3Test {
assertEquals(1, fpOutput.size()); assertEquals(1, fpOutput.size());
bool = (BooleanType) fpOutput.get(0); bool = (BooleanType) fpOutput.get(0);
assertTrue(bool.getValue()); assertTrue(bool.getValue());
// //
// fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()"); // fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()");
// assertEquals(1, fpOutput.size()); // assertEquals(1, fpOutput.size());
// bool = (BooleanType) fpOutput.get(0); // bool = (BooleanType) fpOutput.get(0);
// assertTrue(bool.getValue()); // assertTrue(bool.getValue());
ValidationResult output = myVal.validateWithResult(inputString); ValidationResult output = myVal.validateWithResult(inputString);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
@ -266,8 +265,7 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0; int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) { for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {}:{} {} - {}", ourLog.info("Result {}: {} - {}:{} {} - {}", new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() });
new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() });
index++; index++;
retVal.add(next); retVal.add(next);
@ -482,12 +480,9 @@ public class FhirInstanceValidatorDstu3Test {
@Test @Test
public void testValidateResourceWithDefaultValuesetBadCode() { public void testValidateResourceWithDefaultValuesetBadCode() {
String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n" + " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n" + " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
+ "</Observation>";
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
assertEquals( assertEquals("The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set", output.getMessages().get(0).getMessage());
"The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set",
output.getMessages().get(0).getMessage());
} }
@Test @Test
@ -565,9 +560,7 @@ public class FhirInstanceValidatorDstu3Test {
List<SingleValidationMessage> all = logResultsAndReturnAll(output); List<SingleValidationMessage> all = logResultsAndReturnAll(output);
assertEquals(1, all.size()); assertEquals(1, all.size());
assertEquals("Patient.identifier.type", all.get(0).getLocationString()); assertEquals("Patient.identifier.type", all.get(0).getLocationString());
assertEquals( assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type, and a code should come from this value set unless it has no suitable code", all.get(0).getMessage());
"None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type, and a code should come from this value set unless it has no suitable code",
all.get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity());
} }

View File

@ -154,6 +154,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel); v.setBestPracticeWarningLevel(myBestPracticeWarningLevel);
v.setAnyExtensionsAllowed(true); v.setAnyExtensionsAllowed(true);
v.setRequireResourceId(false);
v.setSuppressLoincSnomedMessages(true);
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<ValidationMessage>();

View File

@ -39,11 +39,11 @@ import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.instance.terminologies.ValueSetExpander.ETooCostly; import org.hl7.fhir.instance.terminologies.ValueSetExpander.ETooCostly;
import org.hl7.fhir.instance.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.instance.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.instance.terminologies.ValueSetExpanderFactory;
import org.hl7.fhir.instance.terminologies.ValueSetExpansionCache;
import org.hl7.fhir.instance.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.instance.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.instance.utilities.Utilities; import org.hl7.fhir.instance.utilities.Utilities;
import org.hl7.fhir.instance.utilities.xml.XMLUtil; import org.hl7.fhir.instance.utilities.xml.XMLUtil;
import org.hl7.fhir.instance.terminologies.ValueSetExpanderFactory;
import org.hl7.fhir.instance.terminologies.ValueSetExpansionCache;
import org.hl7.fhir.instance.utils.EOperationOutcome; import org.hl7.fhir.instance.utils.EOperationOutcome;
import org.hl7.fhir.instance.utils.IWorkerContext; import org.hl7.fhir.instance.utils.IWorkerContext;
import org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult; import org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult;
@ -59,7 +59,6 @@ import com.google.gson.JsonNull;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
/* /*
* todo: * todo:
* check urn's don't start oid: or uuid: * check urn's don't start oid: or uuid:
@ -69,6 +68,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean anyExtensionsAllowed; private boolean anyExtensionsAllowed;
private BestPracticeWarningLevel bpWarnings; private BestPracticeWarningLevel bpWarnings;
private ValueSetExpanderFactory cache;
// configuration items // configuration items
private CheckDisplayOption checkDisplay; private CheckDisplayOption checkDisplay;
private IWorkerContext context; private IWorkerContext context;
@ -84,6 +84,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
super(); super();
this.context = theContext; this.context = theContext;
source = Source.InstanceValidator; source = Source.InstanceValidator;
cache = new ValueSetExpansionCache(theContext, null);
}
public InstanceValidator(IWorkerContext theContext, ValueSetExpanderFactory theValueSetExpander) throws Exception {
super();
this.context = theContext;
source = Source.InstanceValidator;
this.cache = theValueSetExpander;
} }
private boolean allowUnknownExtension(String url) { private boolean allowUnknownExtension(String url) {
@ -185,34 +193,70 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private void checkCodeableConcept(List<ValidationMessage> errors, String path, WrapperElement element, StructureDefinition profile, ElementDefinition theElementCntext) throws EOperationOutcome, Exception { private void checkCodeableConcept(List<ValidationMessage> errors, String path, WrapperElement element, StructureDefinition profile, ElementDefinition theElementCntext)
throws EOperationOutcome, Exception {
if (theElementCntext != null && theElementCntext.hasBinding()) { if (theElementCntext != null && theElementCntext.hasBinding()) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) { if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet valueset = resolveBindingReference(binding.getValueSet()); ValueSet unexpandedVs = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) { if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, unexpandedVs != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
CodeableConcept cc = null; ValueSet vs;
try { try {
cc = readAsCodeableConcept(element); boolean found = false;
if (!cc.hasCoding()) { boolean any = false;
if (binding.getStrength() == BindingStrength.REQUIRED) WrapperElement c = element.getFirstChild();
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()); while (c != null) {
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) if (c.getName().equals("coding")) {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code should be provided from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()); any = true;
String system = c.getNamedChildValue("system");
String code = c.getNamedChildValue("code");
if (system != null && code != null) {
ValueSetExpansionOutcome exp = cache.getExpander().expand(unexpandedVs);
vs = exp != null ? exp.getValueset() : null;
if (vs == null) {
if (binding.getStrength() != BindingStrength.REQUIRED) {
ValidationResult validationResult = context.validateCode(system, code, null);
if (validationResult.isOk()) {
found = true;
} else { } else {
ValidationResult vr = context.validateCode(cc, valueset); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Unable to validate code \"{0}\" in code system \"{1}\"", code, system);
if (!vr.isOk()) { return;
if (binding.getStrength() == BindingStrength.REQUIRED) }
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()+", and a code from this value set is required"); }
}
if (found == false) {
if (!warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "Unable to expand value set for " + describeReference(binding.getValueSet()))) {
return;
}
}
found = found || codeInExpansion(vs, system, code);
}
}
c = c.getNextSibling();
}
if (!any && binding.getStrength() == BindingStrength.REQUIRED)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, found,
"No code provided, and value set " + describeReference(binding.getValueSet()) + " (" + unexpandedVs.getUrl() + ") is required");
if (any)
if (binding.getStrength() == BindingStrength.PREFERRED)
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, found,
"None of the codes are in the example value set " + describeReference(binding.getValueSet()) + " (" + unexpandedVs.getUrl() + ")");
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code"); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, found,
else if (binding.getStrength() == BindingStrength.PREFERRED) "None of the codes are in the expected value set " + describeReference(binding.getValueSet()) + " (" + unexpandedVs.getUrl() + ")");
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set");
}
}
} catch (Exception e) { } catch (Exception e) {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept"); if (e.getMessage() == null) {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,
"Exception opening value set " + unexpandedVs.getUrl() + " for " + describeReference(binding.getValueSet()) + ": --Null--");
// } else if (!e.getMessage().contains("unable to find value set http://snomed.info/sct")) {
// hint(errors, IssueType.CODEINVALID, path, suppressLoincSnomedMessages, "Snomed value set - not validated");
// } else if (!e.getMessage().contains("unable to find value set http://loinc.org")) {
// hint(errors, IssueType.CODEINVALID, path, suppressLoincSnomedMessages, "Loinc value set - not validated");
} else
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,
"Exception opening value set " + unexpandedVs.getUrl() + " for " + describeReference(binding.getValueSet()) + ": " + e.getMessage());
} }
} }
} else if (binding.hasValueSet()) { } else if (binding.hasValueSet()) {
@ -224,25 +268,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private CodeableConcept readAsCodeableConcept(WrapperElement element) {
CodeableConcept cc = new CodeableConcept();
List<WrapperElement> list = new ArrayList<WrapperElement>();
element.getNamedChildren("coding", list);
for (WrapperElement item : list)
cc.addCoding(readAsCoding(item));
cc.setText(element.getNamedChildValue("text"));
return cc;
}
private Coding readAsCoding(WrapperElement item) {
Coding c = new Coding();
c.setSystem(item.getNamedChildValue("system"));
c.setVersion(item.getNamedChildValue("version"));
c.setCode(item.getNamedChildValue("code"));
c.setDisplay(item.getNamedChildValue("display"));
return c;
}
private void checkCoding(List<ValidationMessage> errors, String path, WrapperElement focus, Coding fixed) { private void checkCoding(List<ValidationMessage> errors, String path, WrapperElement focus, Coding fixed) {
checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system"); checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system");
checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code"); checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code");
@ -250,7 +275,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected"); checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected");
} }
private void checkCoding(List<ValidationMessage> errors, String path, WrapperElement element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept) throws EOperationOutcome, Exception { private void checkCoding(List<ValidationMessage> errors, String path, WrapperElement element, StructureDefinition profile, ElementDefinition context) throws EOperationOutcome, Exception {
String code = element.getNamedChildValue("code"); String code = element.getNamedChildValue("code");
String system = element.getNamedChildValue("system"); String system = element.getNamedChildValue("system");
String display = element.getNamedChildValue("display"); String display = element.getNamedChildValue("display");
@ -258,31 +283,37 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (system != null && code != null) { if (system != null && code != null) {
if (checkCode(errors, element, path, code, system, display)) if (checkCode(errors, element, path, code, system, display))
if (theElementCntext != null && theElementCntext.getBinding() != null) { if (context != null && context.getBinding() != null) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding(); ElementDefinitionBindingComponent binding = context.getBinding();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) { if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet valueset = resolveBindingReference(binding.getValueSet()); ValueSet vs = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) { if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
Coding c = null;
try { try {
c = readAsCoding(element); vs = cache.getExpander().expand(vs).getValueset();
ValidationResult vr = context.validateCode(c, valueset); if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "Unable to expand value set for " + describeReference(binding.getValueSet()))) {
if (!vr.isOk()) {
if (binding.getStrength() == BindingStrength.REQUIRED) if (binding.getStrength() == BindingStrength.REQUIRED)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is required from this value set"); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, codeInExpansion(vs, system, code),
"Code {" + system + "}" + code + " is not in value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ")");
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code"); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, codeInExpansion(vs, system, code),
else if (binding.getStrength() == BindingStrength.PREFERRED) "Code {" + system + "}" + code + " is not in value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ")");
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set"); else
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, codeInExpansion(vs, system, code),
"Code {" + system + "}" + code + " is not in value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ")");
} }
} catch (Exception e) { } catch (Exception e) {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept"); if (e.getMessage() == null)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,
"Exception opening value set " + vs.getUrl() + " for " + describeReference(binding.getValueSet()) + ": --Null--");
else
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,
"Exception opening value set " + vs.getUrl() + " for " + describeReference(binding.getValueSet()) + ": " + e.getMessage());
} }
} }
} else if (binding.hasValueSet()) { } else if (binding.hasValueSet()) {
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked"); hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
} else if (!inCodeableConcept) { } else {
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked"); hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
} }
} }
@ -312,7 +343,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (warning(errors, IssueType.INVALID, element.line(), element.col(), p, pr != null, "StructureDefinition reference \"{0}\" could not be resolved", ref)) { if (warning(errors, IssueType.INVALID, element.line(), element.col(), p, pr != null, "StructureDefinition reference \"{0}\" could not be resolved", ref)) {
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), p, pr.hasSnapshot(), if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), p, pr.hasSnapshot(),
"StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) { "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
validateElement(errors, pr, pr.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack, false); validateElement(errors, pr, pr.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack);
} }
} }
i++; i++;
@ -619,28 +650,41 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
// note that we don't check the type here; it could be string, uri or code. // note that we don't check the type here; it could be string, uri or code.
private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, WrapperElement element) throws Exception { private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition context, WrapperElement element) throws Exception {
if (!element.hasAttribute("value")) if (!element.hasAttribute("value"))
return; return;
String value = element.getAttribute("value"); String value = element.getAttribute("value");
String system = null;
String display = null;
// System.out.println("check "+value+" in "+path); // System.out.println("check "+value+" in "+path);
// firstly, resolve the value set // firstly, resolve the value set
ElementDefinitionBindingComponent binding = elementContext.getBinding(); ElementDefinitionBindingComponent binding = context.getBinding();
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) { if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet vs = resolveBindingReference(binding.getValueSet()); ValueSet vs = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) { if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) {
ValidationResult vr = context.validateCode(system, value, display, vs); try {
if (!vr.isOk()) { ValueSetExpansionOutcome expansionOutcome = cache.getExpander().expand(vs);
vs = expansionOutcome != null ? expansionOutcome.getValueset() : null;
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "Unable to expand value set for {0}", describeReference(binding.getValueSet()))) {
boolean ok = codeInExpansion(vs, null, value);
if (binding.getStrength() == BindingStrength.REQUIRED) if (binding.getStrength() == BindingStrength.REQUIRED)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is required from this value set"); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value {0} is not in value set {1} ({2})", value, describeReference(binding.getValueSet()),
vs.getUrl());
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code should come from this value set unless it has no suitable code"); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value {0} is not in value set {1} ({2})", value, describeReference(binding.getValueSet()),
else if (binding.getStrength() == BindingStrength.PREFERRED) vs.getUrl());
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is recommended to come from this value set"); else
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value {0} is not in value set {1} ({2})", value, describeReference(binding.getValueSet()),
vs.getUrl());
}
} catch (ETooCostly e) {
if (e.getMessage() == null)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,
"Exception opening value set " + vs.getUrl() + " for " + describeReference(binding.getValueSet()) + ": --Null--");
else
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,
"Exception opening value set " + vs.getUrl() + " for " + describeReference(binding.getValueSet()) + ": " + e.getMessage());
} }
} }
} else } else
@ -883,7 +927,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return context; return context;
} }
private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition ed, String discriminator, StructureDefinition profile) throws Exception { private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition ed, String discriminator, StructureDefinition profile, List<ValidationMessage> errors) throws Exception {
List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, ed); List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, ed);
List<ElementDefinition> snapshot = null; List<ElementDefinition> snapshot = null;
if (childDefinitions.isEmpty()) { if (childDefinitions.isEmpty()) {
@ -892,14 +936,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
throw new Exception("Error in profile for " + path + " no children, no type"); throw new Exception("Error in profile for " + path + " no children, no type");
if (ed.getType().size() > 1) if (ed.getType().size() > 1)
throw new Exception("Error in profile for " + path + " multiple types defined in slice discriminator"); throw new Exception("Error in profile for " + path + " multiple types defined in slice discriminator");
StructureDefinition type;
String url;
if (ed.getType().get(0).hasProfile()) { if (ed.getType().get(0).hasProfile()) {
// need to do some special processing for reference here... url = ed.getType().get(0).getProfile().get(0).getValue();
if (ed.getType().get(0).getCode().equals("Reference")) } else {
discriminator = discriminator.substring(discriminator.indexOf(".")+1); url = "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode();
type = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue()); }
} else StructureDefinition type = context.fetchResource(StructureDefinition.class, url);
type = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode()); if (type == null) {
super.fail(errors, IssueType.INCOMPLETE, path, false, "Failed to retrieve StructureDefinition with URL: " + url);
return null;
}
snapshot = type.getSnapshot().getElement(); snapshot = type.getSnapshot().getElement();
ed = snapshot.get(0); ed = snapshot.get(0);
} else { } else {
@ -950,8 +998,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private Element getValueForDiscriminator(WrapperElement element, String discriminator, ElementDefinition criteria) { private Element getValueForDiscriminator(WrapperElement element, String discriminator, ElementDefinition criteria) {
// throw new Error("validation of slices not done yet"); throw new Error("validation of slices not done yet");
return null;
} }
private ValueSet getValueSet(String system) throws Exception { private ValueSet getValueSet(String system) throws Exception {
@ -968,25 +1015,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean isAbsolute(String uri) { private boolean isAbsolute(String uri) {
return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:") return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:")
|| uri.startsWith("urn:iso:") || isValidFHIRUrn(uri); || uri.startsWith("urn:iso:");
}
private boolean isValidFHIRUrn(String uri) {
return (uri.equals("urn:x-fhir:uk:id:nhs-number"));
} }
public boolean isAnyExtensionsAllowed() { public boolean isAnyExtensionsAllowed() {
return anyExtensionsAllowed; return anyExtensionsAllowed;
} }
private boolean isParametersEntry(String path) {
String[] parts = path.split("\\/");
if (path.startsWith("/f:"))
return parts.length == 4 && parts[parts.length-3].equals("f:Parameters") && parts[parts.length-2].startsWith("f:parameter") && parts[parts.length-1].startsWith("f:resource");
else
return parts.length == 4 && parts[parts.length-3].equals("Parameters") && parts[parts.length-2].startsWith("parameter") && parts[parts.length-1].startsWith("resource");
}
private boolean isBundleEntry(String path) { private boolean isBundleEntry(String path) {
String[] parts = path.split("\\/"); String[] parts = path.split("\\/");
if (path.startsWith("/f:")) if (path.startsWith("/f:"))
@ -995,6 +1030,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return parts.length > 2 && parts[parts.length - 1].equals("resource") && ((parts.length > 2 && parts[parts.length - 3].equals("entry")) || parts[parts.length - 2].equals("entry")); return parts.length > 2 && parts[parts.length - 1].equals("resource") && ((parts.length > 2 && parts[parts.length - 3].equals("entry")) || parts[parts.length - 2].equals("entry"));
} }
private boolean isParametersEntry(String path) {
String[] parts = path.split("\\/");
if (path.startsWith("/f:"))
return parts.length == 4 && parts[parts.length-3].equals("f:Parameters") && parts[parts.length-2].startsWith("f:parameter") && parts[parts.length-1].startsWith("f:resource");
else
return parts.length == 4 && parts[parts.length-3].equals("Parameters") && parts[parts.length-2].startsWith("parameter") && parts[parts.length-1].startsWith("resource");
}
private boolean isPrimitiveType(String type) { private boolean isPrimitiveType(String type) {
return type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("integer") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("uri") return type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("integer") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("uri")
|| type.equalsIgnoreCase("base64Binary") || type.equalsIgnoreCase("instant") || type.equalsIgnoreCase("date") || type.equalsIgnoreCase("uuid") || type.equalsIgnoreCase("id") || type.equalsIgnoreCase("base64Binary") || type.equalsIgnoreCase("instant") || type.equalsIgnoreCase("date") || type.equalsIgnoreCase("uuid") || type.equalsIgnoreCase("id")
@ -1096,9 +1139,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else { } else {
// split into base, type, and id // split into base, type, and id
String u = null; String u = null;
if (fullUrl != null && fullUrl.endsWith(type+"/"+id)) if (fullUrl != null && fullUrl.endsWith(type + "/" + id))
// fullUrl = complex // fullUrl = complex
u = fullUrl.substring((type+"/"+id).length())+ref; u = fullUrl.substring((type + "/" + id).length()) + ref;
String[] parts = ref.split("\\/"); String[] parts = ref.split("\\/");
if (parts.length >= 2) { if (parts.length >= 2) {
String t = parts[0]; String t = parts[0];
@ -1179,15 +1222,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* - the definition of how slicing is determined * - the definition of how slicing is determined
* @param ed * @param ed
* - the slice for which to test membership * - the slice for which to test membership
* @param errors
* @return * @return
* @throws Exception * @throws Exception
*/ */
private boolean sliceMatches(WrapperElement element, String path, ElementDefinition slice, ElementDefinition ed, StructureDefinition profile) throws Exception { private boolean sliceMatches(WrapperElement element, String path, ElementDefinition slice, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors) throws Exception {
if (!slice.getSlicing().hasDiscriminator()) if (!slice.getSlicing().hasDiscriminator())
return false; // cannot validate in this case return false; // cannot validate in this case
for (StringType s : slice.getSlicing().getDiscriminator()) { for (StringType s : slice.getSlicing().getDiscriminator()) {
String discriminator = s.getValue(); String discriminator = s.getValue();
ElementDefinition criteria = getCriteriaForDiscriminator(path, ed, discriminator, profile); ElementDefinition criteria = getCriteriaForDiscriminator(path, ed, discriminator, profile, errors);
if (criteria == null) {
return false;
}
if (discriminator.equals("url") && criteria.getPath().equals("Extension.url")) { if (discriminator.equals("url") && criteria.getPath().equals("Extension.url")) {
if (!element.getAttribute("url").equals(((UriType) criteria.getFixed()).asStringValue())) if (!element.getAttribute("url").equals(((UriType) criteria.getFixed()).asStringValue()))
return false; return false;
@ -1207,7 +1254,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// profile is valid, and matches the resource name // profile is valid, and matches the resource name
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), profile.hasSnapshot(), if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), profile.hasSnapshot(),
"StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) { "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
validateElement(errors, profile, profile.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack, false); validateElement(errors, profile, profile.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack);
checkDeclaredProfiles(errors, element, stack); checkDeclaredProfiles(errors, element, stack);
@ -1638,7 +1685,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// firstBase = ebase == null ? base : ebase; // firstBase = ebase == null ? base : ebase;
private void validateElement(List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, private void validateElement(List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
WrapperElement element, String actualType, NodeStack stack, boolean inCodeableConcept) throws Exception { WrapperElement element, String actualType, NodeStack stack) throws Exception {
// irrespective of what element it is, it cannot be empty // irrespective of what element it is, it cannot be empty
if (element.isXml()) { if (element.isXml()) {
rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), FormatUtilities.FHIR_NS.equals(element.getNamespace()), rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), FormatUtilities.FHIR_NS.equals(element.getNamespace()),
@ -1679,7 +1726,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
match = nameMatches(ei.name, tail(ed.getPath())); match = nameMatches(ei.name, tail(ed.getPath()));
} else { } else {
if (nameMatches(ei.name, tail(ed.getPath()))) if (nameMatches(ei.name, tail(ed.getPath())))
match = sliceMatches(ei.element, ei.path, slice, ed, profile); match = sliceMatches(ei.element, ei.path, slice, ed, profile, errors);
} }
if (match) { if (match) {
if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null, "Element matches more than one slice")) if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null, "Element matches more than one slice"))
@ -1760,7 +1807,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String localStackLiterapPath = localStack.getLiteralPath(); String localStackLiterapPath = localStack.getLiteralPath();
String eiPath = ei.path; String eiPath = ei.path;
assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiterapPath: " + localStackLiterapPath; assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiterapPath: " + localStackLiterapPath;
boolean thisIsCodeableConcept = false;
if (type != null) { if (type != null) {
if (isPrimitiveType(type)) if (isPrimitiveType(type))
@ -1769,11 +1815,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (type.equals("Identifier")) if (type.equals("Identifier"))
checkIdentifier(errors, ei.path, ei.element, ei.definition); checkIdentifier(errors, ei.path, ei.element, ei.definition);
else if (type.equals("Coding")) else if (type.equals("Coding"))
checkCoding(errors, ei.path, ei.element, profile, ei.definition, inCodeableConcept); checkCoding(errors, ei.path, ei.element, profile, ei.definition);
else if (type.equals("CodeableConcept")) { else if (type.equals("CodeableConcept"))
checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition); checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition);
thisIsCodeableConcept = true; else if (type.equals("Reference"))
} else if (type.equals("Reference"))
checkReference(errors, ei.path, ei.element, profile, ei.definition, actualType, localStack); checkReference(errors, ei.path, ei.element, profile, ei.definition, actualType, localStack);
if (type.equals("Extension")) if (type.equals("Extension"))
@ -1784,13 +1829,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
else { else {
StructureDefinition p = getProfileForType(type); StructureDefinition p = getProfileForType(type);
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type)) { if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type)) {
validateElement(errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, ei.element, type, localStack, thisIsCodeableConcept); validateElement(errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, ei.element, type, localStack);
} }
} }
} }
} else { } else {
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name)) if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name))
validateElement(errors, profile, ei.definition, null, null, ei.element, type, localStack, false); validateElement(errors, profile, ei.definition, null, null, ei.element, type, localStack);
} }
} }
} }
@ -1830,15 +1875,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), profile != null, "No profile found for resource type '" + resourceName + "'"); ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), profile != null, "No profile found for resource type '" + resourceName + "'");
} else { } else {
String type = profile.hasConstrainedType() ? profile.getConstrainedType() : profile.getName(); String type = profile.hasConstrainedType() ? profile.getConstrainedType() : profile.getName();
// special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead
if (!type.equals(resourceName) && resourceName.equals("Bundle")) {
WrapperElement first = getFirstEntry(element);
if (first != null && first.getResourceType().equals(type)) {
element = first;
resourceName = element.getResourceType();
needsId = false;
}
}
ok = rule(errors, IssueType.INVALID, -1, -1, stack.addToLiteralPath(resourceName), type.equals(resourceName), ok = rule(errors, IssueType.INVALID, -1, -1, stack.addToLiteralPath(resourceName), type.equals(resourceName),
"Specified profile type was '" + profile.getConstrainedType() + "', but resource type was '" + resourceName + "'"); "Specified profile type was '" + profile.getConstrainedType() + "', but resource type was '" + resourceName + "'");
} }
@ -1852,18 +1888,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private WrapperElement getFirstEntry(WrapperElement bundle) {
List<WrapperElement> list = new ArrayList<WrapperElement>();
bundle.getNamedChildren("entry", list);
if (list.isEmpty())
return null;
WrapperElement resource = list.get(0).getNamedChild("resource");
if (resource == null)
return null;
else
return resource.getFirstChild();
}
private void validateSections(List<ValidationMessage> errors, List<WrapperElement> entries, WrapperElement focus, NodeStack stack, String fullUrl, String id) { private void validateSections(List<ValidationMessage> errors, List<WrapperElement> entries, WrapperElement focus, NodeStack stack, String fullUrl, String id) {
List<WrapperElement> sections = new ArrayList<WrapperElement>(); List<WrapperElement> sections = new ArrayList<WrapperElement>();
focus.getNamedChildren("entry", sections); focus.getNamedChildren("entry", sections);
@ -1877,8 +1901,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) { private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) {
// throw new Error("validation of slices not done yet"); throw new Error("validation of slices not done yet");
return false;
} }
private boolean yearIsValid(String v) { private boolean yearIsValid(String v) {
@ -2079,6 +2102,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public int line() { public int line() {
return line; return line;
} }
} }
public class ElementInfo { public class ElementInfo {
@ -2148,7 +2172,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private void createChildren() { private void createChildren() {
// System.out.println(" ..: "+path); // System.out.println(" ..: "+path);
// we're going to make this look like the XML // we're going to make this look like the XML
if (element != null) { if (element == null)
throw new Error("not done yet");
if (element instanceof JsonPrimitive) { if (element instanceof JsonPrimitive) {
// we may have an element_ too // we may have an element_ too
if (_element != null && _element instanceof JsonObject) if (_element != null && _element instanceof JsonObject)
@ -2164,10 +2190,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else } else
throw new Error("unexpected condition: " + element.getClass().getName()); throw new Error("unexpected condition: " + element.getClass().getName());
} }
if (_element != null) {
}
}
@Override @Override
public String getAttribute(String name) { public String getAttribute(String name) {

View File

@ -9,12 +9,14 @@ import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport; import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
@ -24,11 +26,13 @@ import org.hl7.fhir.instance.model.Observation;
import org.hl7.fhir.instance.model.Observation.ObservationStatus; import org.hl7.fhir.instance.model.Observation.ObservationStatus;
import org.hl7.fhir.instance.model.Patient; import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.model.StringType; import org.hl7.fhir.instance.model.StringType;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet; import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent; import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.validation.InstanceValidator;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@ -45,7 +49,27 @@ public class FhirInstanceValidatorTest {
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org(); private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidatorTest.class);
private static DefaultProfileValidationSupport ourDefaultValidationSupport = new DefaultProfileValidationSupport(); private static DefaultProfileValidationSupport ourDefaultValidationSupport = new DefaultProfileValidationSupport() {
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
if (theUri.equals("http://fhir.hl7.org.nz/dstu2/StructureDefinition/ohAllergyIntolerance")) {
String contents;
try {
contents = IOUtils.toString(getClass().getResourceAsStream("/allergyintolerance-sd-david.json"), "UTF-8");
} catch (IOException e) {
throw new Error(e);
}
StructureDefinition sd = ourCtx.newJsonParser().parseResource(StructureDefinition.class, contents);
return (T) sd;
}
T retVal = super.fetchResource(theContext, theClass, theUri);
if (retVal == null) {
ourLog.warn("Can not fetch: {}", theUri);
}
return retVal;
}
};
private FhirInstanceValidator myInstanceVal; private FhirInstanceValidator myInstanceVal;
private IValidationSupport myMockSupport; private IValidationSupport myMockSupport;
@ -102,9 +126,10 @@ public class FhirInstanceValidatorTest {
.thenAnswer(new Answer<IBaseResource>() { .thenAnswer(new Answer<IBaseResource>() {
@Override @Override
public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable { public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
IBaseResource retVal = ourDefaultValidationSupport.fetchResource( FhirContext fhirContext = (FhirContext) theInvocation.getArguments()[0];
(FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1], Class<IBaseResource> type = (Class<IBaseResource>) theInvocation.getArguments()[1];
(String) theInvocation.getArguments()[2]); String uri = (String) theInvocation.getArguments()[2];
IBaseResource retVal = ourDefaultValidationSupport.fetchResource(fhirContext, type, uri);
ourLog.info("fetchResource({}, {}) : {}", ourLog.info("fetchResource({}, {}) : {}",
new Object[] { theInvocation.getArguments()[1], theInvocation.getArguments()[2], retVal }); new Object[] { theInvocation.getArguments()[1], theInvocation.getArguments()[2], retVal });
return retVal; return retVal;
@ -188,6 +213,20 @@ public class FhirInstanceValidatorTest {
assertEquals(output.toString(), 0, output.getMessages().size()); assertEquals(output.toString(), 0, output.getMessages().size());
} }
/**
* Received by email from David Hay
*/
@Test
public void testValidateAllergyIntoleranceFromDavid() throws Exception {
String input = IOUtils.toString(getClass().getResourceAsStream("/allergyintolerance-david.json"), "UTF-8");
// Just make sure this doesn't crash
ValidationResult output = myVal.validateWithResult(input);
ourLog.info(output.toString());
}
@Test @Test
public void testValidateRawJsonResourceBadAttributes() { public void testValidateRawJsonResourceBadAttributes() {
// @formatter:off // @formatter:off
@ -327,7 +366,7 @@ public class FhirInstanceValidatorTest {
+ " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>"; + " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
assertEquals( assertEquals(
"The value provided is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set", "Coded value notvalidcode is not in value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status)",
output.getMessages().get(0).getMessage()); output.getMessages().get(0).getMessage());
} }
@ -342,16 +381,16 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(patient); ValidationResult output = myVal.validateWithResult(patient);
List<SingleValidationMessage> all = logResultsAndReturnAll(output); List<SingleValidationMessage> all = logResultsAndReturnAll(output);
assertEquals(1, all.size()); assertEquals(2, all.size());
assertEquals("/f:Patient/f:identifier/f:type", all.get(0).getLocationString()); assertEquals("/f:Patient/f:identifier/f:type", all.get(0).getLocationString());
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type, and a code should come from this value set unless it has no suitable code", all.get(0).getMessage()); assertEquals("None of the codes are in the expected value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type)", all.get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity());
patient = new Patient(); patient = new Patient();
patient.addIdentifier().setSystem("http://system").setValue("12345").getType().addCoding().setSystem("http://hl7.org/fhir/v2/0203").setCode("MR"); patient.addIdentifier().setSystem("http://system").setValue("12345").getType().addCoding().setSystem("http://hl7.org/fhir/v2/0203").setCode("MR");
output = myVal.validateWithResult(patient); output = myVal.validateWithResult(patient);
all = logResultsAndReturnAll(output); all = logResultsAndReturnNonInformationalOnes(output);
assertEquals(0, all.size()); assertEquals(0, all.size());
} }
@ -367,8 +406,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 0, errors.size()); assertEquals(errors.toString(), 1, errors.size());
assertEquals("Unable to validate code \"12345\" in code system \"http://loinc.org\"", errors.get(0).getMessage());
} }
@Test @Test
@ -402,8 +441,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 0, errors.size()); assertEquals(errors.toString(), 1, errors.size());
assertEquals("Unable to validate code \"1234\" in code system \"http://loinc.org\"", errors.get(0).getMessage());
} }
@ -418,7 +457,7 @@ public class FhirInstanceValidatorTest {
input.getCode().addCoding().setSystem("http://acme.org").setCode("12345"); input.getCode().addCoding().setSystem("http://acme.org").setCode("12345");
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnAll(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 0, errors.size()); assertEquals(errors.toString(), 0, errors.size());
} }

View File

@ -0,0 +1,152 @@
{
"resourceType": "AllergyIntolerance",
"id": "84651",
"meta": {
"versionId": "1",
"lastUpdated": "2016-05-19T15:43:49.815-04:00",
"profile": [
"http://fhir.hl7.org.nz/dstu2/StructureDefinition/ohAllergyIntolerance"
]
},
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-endDate",
"valueDateTime": "2016-05-03"
},
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-informantRole",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "133932002",
"display": "Carer"
}
]
}
},
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-reasonClosed",
"valueCodeableConcept": {
"coding": [
{
"system": "http://orionhealth.com/problem-list/adverse-reaction-reason-closed",
"code": "1",
"display": "Disproven"
}
]
}
},
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-classification",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "419076005",
"display": "Allergic reaction"
}
]
}
}
],
"language": [
"en"
],
"identifier": [
{
"system": "http://orionhealth.com/identifier/problem-list/ORGNISATION_ORION",
"value": "2.25.148135789146703435349240718177383549558.8.4.4.0/522911"
}
],
"patient": {
"reference": "Patient/64909"
},
"substance": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "84430008",
"display": "Cross reacting antibody"
}
]
},
"status": "inactive",
"criticality": "CRITH",
"category": "environment",
"reaction": [
{
"certainty": "likely",
"_certainty": {
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/originalValue",
"valueCodeableConcept": {
"coding": [
{
"system": "http://orionhealth.com/problem-list/adverse-reaction-certainty",
"code": "SUSPECTED",
"display": "Suspected"
}
]
}
}
]
},
"manifestation": [
{
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-manifestationSeverity",
"valueCodeableConcept": {
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/900000000000207008/version/20150531",
"code": "442452003",
"display": "Life-Threatening"
}
]
}
}
],
"coding": [
{
"system": "http://orionhealth.com/problem-list/clinical-manifestation",
"code": "FT",
"display": "rlo"
}
]
},
{
"extension": [
{
"url": "http://fhir.hl7.org.nz/dstu2/StructureDefinition/al-manifestationSeverity",
"valueCodeableConcept": {
"coding": [
{
"system": "http://orionhealth.com/problem-list/severity",
"code": "UNKNOWN",
"display": "Unknown"
}
]
}
}
],
"coding": [
{
"system": "http://snomed.info/sct",
"version": "http://snomed.info/sct/32506021000036107/version/20150531",
"code": "95332009",
"display": "Rash of systemic lupus erythematosus"
}
]
}
],
"severity": "severe"
}
]
}

View File

@ -234,6 +234,9 @@
had any extensions. Thanks to GitHub user @Virdulys for submitting and had any extensions. Thanks to GitHub user @Virdulys for submitting and
providing a test case! providing a test case!
</action> </action>
<action type="add">
Update DSTU2 InstanceValidator to latest version from upstream
</action>
</release> </release>
<release version="1.5" date="2016-04-20"> <release version="1.5" date="2016-04-20">
<action type="fix" issue="339"> <action type="fix" issue="339">

1
src/site/resources/CNAME Normal file
View File

@ -0,0 +1 @@
hapifhir.io