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.setHardTagListLimit(1000);
myDaoConfig.setIncludeLimit(2000);
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
}
@Before

View File

@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
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.HttpPost;
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.IIdType;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import ca.uhn.fhir.model.api.Bundle;
@ -2185,6 +2187,53 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
response.close();
}
}
/**
* 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
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);
}
@Test
// @Ignore
public void testValidateBuiltInProfiles() throws Exception {
@ -94,7 +93,7 @@ public class FhirInstanceValidatorDstu3Test {
vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/org/hl7/fhir/instance/model/dstu3/profile/" + name + ".xml"), "UTF-8");
TreeSet<String> ids = new TreeSet<String>();
bundle = ourCtx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents);
for (BundleEntryComponent i : bundle.getEntry()) {
org.hl7.fhir.dstu3.model.Resource next = i.getResource();
@ -126,28 +125,28 @@ public class FhirInstanceValidatorDstu3Test {
}
@Test
@Ignore
@Ignore
public void testValidateBundleWithObservations() throws Exception {
String name = "profiles-resources";
ourLog.info("Uploading " + name);
String inputString;
inputString = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/brian_reinhold_bundle.json"), "UTF-8");
Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, inputString);
FHIRPathEngine fp = new FHIRPathEngine(new HapiWorkerContext(ourCtx, myDefaultValidationSupport));
List<Base> fpOutput;
BooleanType bool;
fpOutput = fp.evaluate(bundle.getEntry().get(0).getResource(), "component.where(code = %resource.code).empty()");
assertEquals(1, fpOutput.size());
bool = (BooleanType) fpOutput.get(0);
assertTrue(bool.getValue());
//
// fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()");
// assertEquals(1, fpOutput.size());
// bool = (BooleanType) fpOutput.get(0);
// assertTrue(bool.getValue());
//
// fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()");
// assertEquals(1, fpOutput.size());
// bool = (BooleanType) fpOutput.get(0);
// assertTrue(bool.getValue());
ValidationResult output = myVal.validateWithResult(inputString);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors, empty());
@ -266,8 +265,7 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {}:{} {} - {}",
new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() });
ourLog.info("Result {}: {} - {}:{} {} - {}", new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() });
index++;
retVal.add(next);
@ -482,12 +480,9 @@ public class FhirInstanceValidatorDstu3Test {
@Test
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"
+ "</Observation>";
String input = "<Observation xmlns=\"http://hl7.org/fhir\">\n" + " <status value=\"notvalidcode\"/>\n" + " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
ValidationResult output = myVal.validateWithResult(input);
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());
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());
}
@Test
@ -565,9 +560,7 @@ public class FhirInstanceValidatorDstu3Test {
List<SingleValidationMessage> all = logResultsAndReturnAll(output);
assertEquals(1, all.size());
assertEquals("Patient.identifier.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 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());
}

View File

@ -154,6 +154,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel);
v.setAnyExtensionsAllowed(true);
v.setRequireResourceId(false);
v.setSuppressLoincSnomedMessages(true);
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.terminologies.ValueSetExpander.ETooCostly;
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.Utilities;
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.IWorkerContext;
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.JsonPrimitive;
/*
* todo:
* check urn's don't start oid: or uuid:
@ -69,6 +68,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean anyExtensionsAllowed;
private BestPracticeWarningLevel bpWarnings;
private ValueSetExpanderFactory cache;
// configuration items
private CheckDisplayOption checkDisplay;
private IWorkerContext context;
@ -84,6 +84,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
super();
this.context = theContext;
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) {
@ -185,35 +193,71 @@ 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()) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet valueset = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
CodeableConcept cc = null;
try {
cc = readAsCodeableConcept(element);
if (!cc.hasCoding()) {
if (binding.getStrength() == BindingStrength.REQUIRED)
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());
else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
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());
} else {
ValidationResult vr = context.validateCode(cc, valueset);
if (!vr.isOk()) {
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");
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");
else if (binding.getStrength() == BindingStrength.PREFERRED)
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");
ValueSet unexpandedVs = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, unexpandedVs != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
ValueSet vs;
try {
boolean found = false;
boolean any = false;
WrapperElement c = element.getFirstChild();
while (c != null) {
if (c.getName().equals("coding")) {
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 {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Unable to validate code \"{0}\" in code system \"{1}\"", code, system);
return;
}
}
}
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);
}
}
}
} catch (Exception e) {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept");
}
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)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, found,
"None of the codes are in the expected value set " + describeReference(binding.getValueSet()) + " (" + unexpandedVs.getUrl() + ")");
} catch (Exception e) {
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()) {
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
@ -224,33 +268,14 @@ 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 + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code");
checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display");
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 system = element.getNamedChildValue("system");
String display = element.getNamedChildValue("display");
@ -258,31 +283,37 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (system != null && code != null) {
if (checkCode(errors, element, path, code, system, display))
if (theElementCntext != null && theElementCntext.getBinding() != null) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (context != null && context.getBinding() != null) {
ElementDefinitionBindingComponent binding = context.getBinding();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet valueset = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
Coding c = null;
try {
c = readAsCoding(element);
ValidationResult vr = context.validateCode(c, valueset);
if (!vr.isOk()) {
ValueSet vs = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
try {
vs = cache.getExpander().expand(vs).getValueset();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "Unable to expand value set for " + describeReference(binding.getValueSet()))) {
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)
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");
else if (binding.getStrength() == BindingStrength.PREFERRED)
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");
}
} catch (Exception e) {
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept");
}
warning(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
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) {
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()) {
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");
}
}
@ -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 (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")) {
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++;
@ -594,7 +625,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.getAttribute("value")), "The value '" + e.getAttribute("value") + "' does not have a valid year");
rule(errors, IssueType.INVALID, e.line(), e.col(), path,
e.getAttribute("value")
.matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),
.matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),
"Not a valid date time");
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.getAttribute("value")) || hasTimeZone(e.getAttribute("value")), "if a date has a time, it must have a timezone");
@ -619,29 +650,42 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
// 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"))
return;
String value = element.getAttribute("value");
String system = null;
String display = null;
// System.out.println("check "+value+" in "+path);
// firstly, resolve the value set
ElementDefinitionBindingComponent binding = elementContext.getBinding();
ElementDefinitionBindingComponent binding = context.getBinding();
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet vs = resolveBindingReference(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);
if (!vr.isOk()) {
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");
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");
else if (binding.getStrength() == BindingStrength.PREFERRED)
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");
}
try {
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)
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)
warning(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
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
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding has no source, so can't be checked");
@ -746,7 +790,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return true;
if (codeinExpansion(c, system, code))
return true;
}
}
return false;
}
@ -810,11 +854,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return base + id;
else
return Utilities.appendSlash(base) + type + "/" + id;
}
}
public BestPracticeWarningLevel getBasePracticeWarningLevel() {
return bpWarnings;
}
}
private String getBaseType(StructureDefinition profile, String pr) throws EOperationOutcome, Exception {
// if (pr.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
@ -836,17 +880,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return checkDisplay;
}
// private String findProfileTag(WrapperElement element) {
// String uri = null;
// List<WrapperElement> list = new ArrayList<WrapperElement>();
// element.getNamedChildren("category", list);
// for (WrapperElement c : list) {
// if ("http://hl7.org/fhir/tag/profile".equals(c.getAttribute("scheme"))) {
// uri = c.getAttribute("term");
// }
// }
// return uri;
// }
// private String findProfileTag(WrapperElement element) {
// String uri = null;
// List<WrapperElement> list = new ArrayList<WrapperElement>();
// element.getNamedChildren("category", list);
// for (WrapperElement c : list) {
// if ("http://hl7.org/fhir/tag/profile".equals(c.getAttribute("scheme"))) {
// uri = c.getAttribute("term");
// }
// }
// return uri;
// }
private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
if (code.equals(c.getCode()))
@ -875,7 +919,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
WrapperElement res = we.isXml() ? we.getFirstChild() : we;
if (id.equals(res.getNamedChildValue("id")))
return res;
}
}
return null;
}
@ -883,7 +927,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
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> snapshot = null;
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");
if (ed.getType().size() > 1)
throw new Exception("Error in profile for " + path + " multiple types defined in slice discriminator");
StructureDefinition type;
String url;
if (ed.getType().get(0).hasProfile()) {
// need to do some special processing for reference here...
if (ed.getType().get(0).getCode().equals("Reference"))
discriminator = discriminator.substring(discriminator.indexOf(".")+1);
type = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile().get(0).getValue());
} else
type = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode());
url = ed.getType().get(0).getProfile().get(0).getValue();
} else {
url = "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode();
}
StructureDefinition type = context.fetchResource(StructureDefinition.class, url);
if (type == null) {
super.fail(errors, IssueType.INCOMPLETE, path, false, "Failed to retrieve StructureDefinition with URL: " + url);
return null;
}
snapshot = type.getSnapshot().getElement();
ed = snapshot.get(0);
} else {
@ -943,15 +991,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
return null;
}
}
private StructureDefinition getProfileForType(String type) throws Exception {
return context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type);
}
private Element getValueForDiscriminator(WrapperElement element, String discriminator, ElementDefinition criteria) {
// throw new Error("validation of slices not done yet");
return null;
throw new Error("validation of slices not done yet");
}
private ValueSet getValueSet(String system) throws Exception {
@ -968,17 +1015,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
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:")
|| uri.startsWith("urn:iso:") || isValidFHIRUrn(uri);
}
private boolean isValidFHIRUrn(String uri) {
return (uri.equals("urn:x-fhir:uk:id:nhs-number"));
|| uri.startsWith("urn:iso:");
}
public boolean isAnyExtensionsAllowed() {
return anyExtensionsAllowed;
}
private boolean isBundleEntry(String path) {
String[] parts = path.split("\\/");
if (path.startsWith("/f:"))
return parts.length > 2 && parts[parts.length - 1].startsWith("f:resource") && (parts[parts.length - 2].equals("f:entry") || parts[parts.length - 2].startsWith("f:entry["));
else
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:"))
@ -987,14 +1038,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
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) {
String[] parts = path.split("\\/");
if (path.startsWith("/f:"))
return parts.length > 2 && parts[parts.length - 1].startsWith("f:resource") && (parts[parts.length - 2].equals("f:entry") || parts[parts.length - 2].startsWith("f:entry["));
else
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 isPrimitiveType(String type) {
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")
@ -1086,7 +1129,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private WrapperElement resolveInBundle(List<WrapperElement> entries, String ref, String fullUrl, String type, String id) {
if (Utilities.isAbsoluteUrl(ref)) {
// if the reference is absolute, then you resolve by fullUrl. No other thinking is required.
// if the reference is absolute, then you resolve by fullUrl. No other thinking is required.
for (WrapperElement entry : entries) {
String fu = entry.getNamedChildValue("fullUrl");
if (ref.equals(fu))
@ -1096,9 +1139,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
// split into base, type, and id
String u = null;
if (fullUrl != null && fullUrl.endsWith(type+"/"+id))
if (fullUrl != null && fullUrl.endsWith(type + "/" + id))
// fullUrl = complex
u = fullUrl.substring((type+"/"+id).length())+ref;
u = fullUrl.substring((type + "/" + id).length()) + ref;
String[] parts = ref.split("\\/");
if (parts.length >= 2) {
String t = parts[0];
@ -1179,15 +1222,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* - the definition of how slicing is determined
* @param ed
* - the slice for which to test membership
* @param errors
* @return
* @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())
return false; // cannot validate in this case
for (StringType s : slice.getSlicing().getDiscriminator()) {
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 (!element.getAttribute("url").equals(((UriType) criteria.getFixed()).asStringValue()))
return false;
@ -1207,7 +1254,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// profile is valid, and matches the resource name
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")) {
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);
@ -1266,7 +1313,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
List<ValidationMessage> results = new ArrayList<ValidationMessage>();
validate(results, document, profile);
return results;
}
}
@Override
public List<ValidationMessage> validate(Element element) throws Exception {
@ -1314,7 +1361,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public void validate(List<ValidationMessage> errors, Document document) throws Exception {
checkForProcessingInstruction(errors, document);
validateResource(errors, new DOMWrapperElement(document.getDocumentElement()), null, requiresResourceId, null);
}
}
@Override
public void validate(List<ValidationMessage> errors, Document document, String profile) throws Exception {
@ -1638,7 +1685,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// firstBase = ebase == null ? base : ebase;
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
if (element.isXml()) {
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()));
} else {
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 (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null, "Element matches more than one slice"))
@ -1742,7 +1789,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
TypeRefComponent trc = ei.definition.getType().get(0);
if (trc.getCode().equals("Reference"))
type = "Reference";
else
else
rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false,
"The element " + ei.name + " is illegal. Valid types at this point are " + describeTypes(ei.definition.getType()));
}
@ -1760,7 +1807,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String localStackLiterapPath = localStack.getLiteralPath();
String eiPath = ei.path;
assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiterapPath: " + localStackLiterapPath;
boolean thisIsCodeableConcept = false;
if (type != null) {
if (isPrimitiveType(type))
@ -1769,28 +1815,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (type.equals("Identifier"))
checkIdentifier(errors, ei.path, ei.element, ei.definition);
else if (type.equals("Coding"))
checkCoding(errors, ei.path, ei.element, profile, ei.definition, inCodeableConcept);
else if (type.equals("CodeableConcept")) {
checkCoding(errors, ei.path, ei.element, profile, ei.definition);
else if (type.equals("CodeableConcept"))
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);
if (type.equals("Extension"))
checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack);
else if (type.equals("Resource"))
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path) && !isParametersEntry(ei.path)); // if
// (str.matches(".*([.,/])work\\1$"))
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path) && !isParametersEntry(ei.path)); // if
// (str.matches(".*([.,/])work\\1$"))
else {
StructureDefinition p = getProfileForType(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 {
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 + "'");
} else {
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),
"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) {
List<WrapperElement> sections = new ArrayList<WrapperElement>();
focus.getNamedChildren("entry", sections);
@ -1877,8 +1901,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) {
// throw new Error("validation of slices not done yet");
return false;
throw new Error("validation of slices not done yet");
}
private boolean yearIsValid(String v) {
@ -2079,6 +2102,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public int line() {
return line;
}
}
public class ElementInfo {
@ -2148,25 +2172,23 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private void createChildren() {
// System.out.println(" ..: "+path);
// we're going to make this look like the XML
if (element != null) {
if (element instanceof JsonPrimitive) {
// we may have an element_ too
if (_element != null && _element instanceof JsonObject)
for (Entry<String, JsonElement> t : ((JsonObject) _element).entrySet())
processChild(t.getKey(), t.getValue());
} else if (element instanceof JsonObject) {
for (Entry<String, JsonElement> t : ((JsonObject) element).entrySet())
if (!t.getKey().equals("resourceType")) {
processChild(t.getKey(), t.getValue());
}
} else if (element instanceof JsonNull) {
// nothing to do
} else
throw new Error("unexpected condition: " + element.getClass().getName());
}
if (_element != null) {
if (element == null)
throw new Error("not done yet");
}
if (element instanceof JsonPrimitive) {
// we may have an element_ too
if (_element != null && _element instanceof JsonObject)
for (Entry<String, JsonElement> t : ((JsonObject) _element).entrySet())
processChild(t.getKey(), t.getValue());
} else if (element instanceof JsonObject) {
for (Entry<String, JsonElement> t : ((JsonObject) element).entrySet())
if (!t.getKey().equals("resourceType")) {
processChild(t.getKey(), t.getValue());
}
} else if (element instanceof JsonNull) {
// nothing to do
} else
throw new Error("unexpected condition: " + element.getClass().getName());
}
@Override
@ -2281,7 +2303,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
@Override
public boolean isXml() {
return false;
}
}
@Override
public int line() {
@ -2330,7 +2352,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public NodeStack(boolean xml) {
this.xml = xml;
}
}
public String addToLiteralPath(String... path) {
StringBuilder b = new StringBuilder();
@ -2344,8 +2366,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
b.append("/f:");
b.append(p);
}
}
}
}
} else {
for (String p : path) {
b.append("/");
@ -2353,9 +2375,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
b.append(p.substring(1));
} else {
b.append(p);
}
}
}
}
}
return b.toString();
}

View File

@ -9,12 +9,14 @@ import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.FhirInstanceValidator;
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.Patient;
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.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.validation.InstanceValidator;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
@ -45,7 +49,27 @@ public class FhirInstanceValidatorTest {
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
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 IValidationSupport myMockSupport;
@ -102,9 +126,10 @@ public class FhirInstanceValidatorTest {
.thenAnswer(new Answer<IBaseResource>() {
@Override
public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
IBaseResource retVal = ourDefaultValidationSupport.fetchResource(
(FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1],
(String) theInvocation.getArguments()[2]);
FhirContext fhirContext = (FhirContext) theInvocation.getArguments()[0];
Class<IBaseResource> type = (Class<IBaseResource>) theInvocation.getArguments()[1];
String uri = (String) theInvocation.getArguments()[2];
IBaseResource retVal = ourDefaultValidationSupport.fetchResource(fhirContext, type, uri);
ourLog.info("fetchResource({}, {}) : {}",
new Object[] { theInvocation.getArguments()[1], theInvocation.getArguments()[2], retVal });
return retVal;
@ -188,6 +213,20 @@ public class FhirInstanceValidatorTest {
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
public void testValidateRawJsonResourceBadAttributes() {
// @formatter:off
@ -327,7 +366,7 @@ public class FhirInstanceValidatorTest {
+ " <code>\n" + " <text value=\"No code here!\"/>\n" + " </code>\n" + "</Observation>";
ValidationResult output = myVal.validateWithResult(input);
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());
}
@ -342,16 +381,16 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(patient);
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("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());
patient = new Patient();
patient.addIdentifier().setSystem("http://system").setValue("12345").getType().addCoding().setSystem("http://hl7.org/fhir/v2/0203").setCode("MR");
output = myVal.validateWithResult(patient);
all = logResultsAndReturnAll(output);
all = logResultsAndReturnNonInformationalOnes(output);
assertEquals(0, all.size());
}
@ -367,8 +406,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input);
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
@ -402,8 +441,8 @@ public class FhirInstanceValidatorTest {
ValidationResult output = myVal.validateWithResult(input);
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");
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnAll(output);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
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
providing a test case!
</action>
<action type="add">
Update DSTU2 InstanceValidator to latest version from upstream
</action>
</release>
<release version="1.5" date="2016-04-20">
<action type="fix" issue="339">

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

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