1. Cleaned up test cases to work after version changes in R5 (not sure what to do with uk one and patient-contained-org I'll work on next)

2. Added new test case for (and fixed issue with) validation problems when there are multiple target profiles declared that have the same resource type.  (Previously, the validator was always choosing the first profile, even if it was invalid.)
3. Fixed problems where certain profiles were triggering silent NPEs in the validator and improved console messaging so you can tell what warnings and errors are being generated for what.
This commit is contained in:
Lloyd McKenzie 2019-06-04 20:50:49 -04:00
parent f08b313cfc
commit 2a2005f93b
6 changed files with 187 additions and 47 deletions

View File

@ -405,7 +405,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
StructureDefinition sd = null;
if (profile.startsWith("#")) {
if (!rule(errors, IssueType.INVALID, element.line(), element.col(), path, sd != null, "StructureDefinition reference \"{0}\" is local, but there is not local context", profile)) {
if (!rule(errors, IssueType.INVALID, element.line(), element.col(), path, containingProfile != null, "StructureDefinition reference \"{0}\" is local, but there is not local context", profile)) {
return false;
}
@ -1863,33 +1863,62 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// we validate as much as we can. First, can we infer a type from the profile?
if (!type.hasTargetProfile() || type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource"))
ok = true;
else for (UriType u : type.getTargetProfile()) {
String pr = u.getValue();
String bt = getBaseType(profile, pr);
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + bt);
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) {
b.append(bt);
ok = bt.equals(ft);
if (ok && we!=null && pol.checkValid()) {
doResourceProfile(hostContext, we, pr, errors, stack.push(we, -1, null, null), path, element, profile);
else {
List<StructureDefinition> candidateProfiles = new ArrayList<StructureDefinition>();
for (UriType u : type.getTargetProfile()) {
String pr = u.getValue();
String bt = getBaseType(profile, pr);
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + bt);
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) {
b.append(bt);
if (bt.equals(ft)) {
ok = true;
if (we!=null && pol.checkValid())
candidateProfiles.add(sd);
}
}
}
HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>();
List<List<ValidationMessage>> badProfiles = new ArrayList<List<ValidationMessage>>();
List<String> profiles = new ArrayList<String>();
if (!candidateProfiles.isEmpty()) {
for (StructureDefinition sd: candidateProfiles) {
profiles.add(sd.getUrl());
List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
doResourceProfile(hostContext, we, sd.getUrl(), profileErrors, stack.push(we, -1, null, null), path, element, profile);
if (hasErrors(profileErrors))
badProfiles.add(profileErrors);
else
goodProfiles.put(sd.getUrl(), profileErrors);
if (type.hasAggregation()) {
boolean modeOk = false;
for (Enumeration<AggregationMode> mode : type.getAggregation()) {
if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
modeOk = true;
else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
modeOk = true;
else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled")||refType.equals("remote")))
modeOk = true;
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference");
}
}
if (goodProfiles.size()==1) {
errors.addAll(goodProfiles.values().iterator().next());
} else if (goodProfiles.size()==0) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Unable to find matching profile among choices: " + StringUtils.join("; ", profiles));
for (List<ValidationMessage> messages : badProfiles) {
errors.addAll(messages);
}
} else {
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles among choices: " + StringUtils.join("; ", goodProfiles.keySet()));
for (List<ValidationMessage> messages : goodProfiles.values()) {
errors.addAll(messages);
}
}
} else
ok = true; // suppress following check
if (ok && type.hasAggregation()) {
boolean modeOk;
for (Enumeration<AggregationMode> mode : type.getAggregation()) {
if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
ok = true;
else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
ok = true;
else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled")||refType.equals("remote")))
ok = true;
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference");
}
if (ok)
break;
}
}
if (!ok && type.getCode().equals("*")) {
@ -4001,14 +4030,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + typeProfile)) {
List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay);
boolean hasError = false;
for (ValidationMessage msg : profileErrors) {
if (msg.getLevel()==ValidationMessage.IssueSeverity.ERROR || msg.getLevel()==ValidationMessage.IssueSeverity.FATAL) {
hasError = true;
break;
}
}
if (hasError)
if (hasErrors(profileErrors))
badProfiles.add(profileErrors);
else
goodProfiles.put(typeProfile, profileErrors);

View File

@ -101,7 +101,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
@SuppressWarnings("deprecation")
@Test
public void test() throws Exception {
System.out.println("Name: " + name);
String v = "5.0";
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
if (content.has("version"))
v = content.get("version").getAsString();
@ -138,8 +140,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
if (content.has("profiles")) {
for (JsonElement je : content.getAsJsonArray("profiles")) {
String p = je.getAsString();
System.out.println("Profile: " + p);
String filename = TestUtilities.resourceNameToFile("validation-examples", p);
StructureDefinition sd = loadProfile(filename, v);
StructureDefinition sd = loadProfile(filename, v, messages);
val.getContext().cacheResource(sd);
}
}
@ -154,7 +157,7 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
JsonObject profile = content.getAsJsonObject("profile");
String filename = TestUtilities.resourceNameToFile("validation-examples", profile.get("source").getAsString());
v = content.has("version") ? content.get("version").getAsString() : Constants.VERSION;
StructureDefinition sd = loadProfile(filename, v);
StructureDefinition sd = loadProfile(filename, v, messages);
if (name.startsWith("Json."))
val.validate(null, errorsProfile, new FileInputStream(path), FhirFormat.JSON, sd);
else
@ -163,14 +166,23 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
}
}
public StructureDefinition loadProfile(String filename, String v) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
public StructureDefinition loadProfile(String filename, String v, List<ValidationMessage> messages) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
StructureDefinition sd = (StructureDefinition) loadResource(filename, v);
ProfileUtilities pu = new ProfileUtilities(TestingUtilities.context(), messages, null);
if (!sd.hasSnapshot()) {
ProfileUtilities pu = new ProfileUtilities(TestingUtilities.context(), null, null);
StructureDefinition base = TestingUtilities.context().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
pu.generateSnapshot(base, sd, sd.getUrl(), null, sd.getTitle());
// (debugging) new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", sd.getId()+".xml")), sd);
}
for (Resource r: sd.getContained()) {
if (r instanceof StructureDefinition) {
StructureDefinition childSd = (StructureDefinition)r;
if (!childSd.hasSnapshot()) {
StructureDefinition base = TestingUtilities.context().fetchResource(StructureDefinition.class, childSd.getBaseDefinition());
pu.generateSnapshot(base, childSd, childSd.getUrl(), null, childSd.getTitle());
}
}
}
return sd;
}

View File

@ -360,6 +360,7 @@
]
},
"slice-by-polymorphic-type.xml": {
"version": "4.0",
"errorCount": 0,
"profile": {
"source": "slice-by-polymorphic-type-profile.xml",
@ -411,6 +412,7 @@
}
},
"slicing-types-by-string.xml": {
"version": "4.0",
"errorCount": 0,
"profile": {
"source": "slicing-types-by-string-profile.xml",
@ -729,13 +731,20 @@
"source": "patient-translated-codes.profile.xml",
"errorCount": 0,
"warningCount": 0
},
"patient-contained-org.xml": {
"errorCount": 0,
"profile": {
"source": "patient-contained-org-profile.xml",
"errorCount": 1
}
}
},
"patient-contained-org.xml": {
"errorCount": 0,
"profile": {
"source": "patient-contained-org-profile.xml",
"errorCount": 1
}
},
"multi-profile-same-resource.xml": {
"errorCount": 0,
"profile": {
"source": "multi-profile-same-resource-profile.xml",
"errorCount": 0
}
}
}

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<StructureDefinition xmlns="http://hl7.org/fhir">
<id value="multi-profile-same-resource-profile"/>
<contained>
<StructureDefinition>
<id value="pract1"/>
<url value="http://hl7.org/fhir/StructureDefinition/multi-profile-same-resource-profile-pract1"/>
<name value="MultiProfileSameResourceProfilePract1"/>
<status value="draft"/>
<kind value="resource"/>
<abstract value="false"/>
<type value="Practitioner"/>
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/Practitioner"/>
<derivation value="constraint"/>
<differential>
<element id="Practitioner">
<path value="Practitioner"/>
</element>
<element id="Practitioner.name">
<path value="Practitioner.name"/>
<min value="1"/>
</element>
<element id="Practitioner.gender">
<path value="Practitioner.gender"/>
<max value="0"/>
</element>
</differential>
</StructureDefinition>
</contained>
<contained>
<StructureDefinition>
<id value="pract2"/>
<url value="http://hl7.org/fhir/StructureDefinition/multi-profile-same-resource-profile-pract2"/>
<name value="MultiProfileSameResourceProfilePract2"/>
<status value="draft"/>
<kind value="resource"/>
<abstract value="false"/>
<type value="Practitioner"/>
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/Practitioner"/>
<derivation value="constraint"/>
<differential>
<element id="Practitioner">
<path value="Practitioner"/>
</element>
<element id="Practitioner.name">
<path value="Practitioner.name"/>
<max value="0"/>
</element>
<element id="Practitioner.gender">
<path value="Practitioner.gender"/>
<min value="1"/>
</element>
</differential>
</StructureDefinition>
</contained>
<url value="http://hl7.org/fhir/StructureDefinition/multi-profile-same-resource-profile"/>
<name value="MultiProfileSameResourceProfile"/>
<status value="draft"/>
<description value="Profile with multiple profiles allowed for teh same resource"/>
<kind value="resource"/>
<abstract value="false"/>
<type value="Patient"/>
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/Patient"/>
<derivation value="constraint"/>
<differential>
<element id="Patient">
<path value="Patient"/>
</element>
<element id="Patient.generalPractitioner">
<path value="Patient.generalPractitioner"/>
<min value="1"/>
<max value="1"/>
<type>
<code value="Reference"/>
<targetProfile value="#pract1"/>
<targetProfile value="#pract2"/>
</type>
</element>
</differential>
</StructureDefinition>

View File

@ -0,0 +1,17 @@
<Patient xmlns="http://hl7.org/fhir">
<id value="multi-profile-same-resource"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
</div>
</text>
<contained>
<Practitioner>
<id value="practitioner"/>
<gender value="other"/>
</Practitioner>
</contained>
<generalPractitioner>
<reference value="#practitioner"/>
</generalPractitioner>
</Patient>

View File

@ -2,7 +2,7 @@
"resourceType": "Encounter",
"id": "f003",
"class" : {
"system" : "http://hl7.org/fhir/v3/ActCode",
"system" : "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code" : "AMB"
},
"text": {
@ -22,7 +22,7 @@
"reference": "Patient/f001",
"display": "P. van de Heuvel"
},
"reason": {
"reasonCode": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor",