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:
parent
f08b313cfc
commit
2a2005f93b
|
@ -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();
|
||||
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);
|
||||
ok = bt.equals(ft);
|
||||
if (ok && we!=null && pol.checkValid()) {
|
||||
doResourceProfile(hostContext, we, pr, errors, stack.push(we, -1, null, null), path, element, profile);
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue