Merge pull request #821 from hapifhir/gg-202205-validation

Gg 202205 validation
This commit is contained in:
Grahame Grieve 2022-05-27 15:15:27 +10:00 committed by GitHub
commit 327d73aae5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 592 additions and 45 deletions

View File

@ -1,9 +1,18 @@
## Validator Changes
* alter per-1 to handle different precision on start/end
* Significant improvement in performance of validation (10-100 fold for simple resources)
* Add output tracker to trask progress of validation (Validate %R against %P..........20..........40..........60..........80.........|)
* Alter per-1 to handle different precision on start/end
* Add support for a -jurisdiction parameter, preparing for jurisdictionally specific constraints in profiles
* Fix bug in snapshot generation where type slices on a mandatory element were all marked as mandatory
* Add warnings when potential matches are found when performing reference resolution in bundles
## Other code changes
* extend FHIRPath to support lowBoundary(), highBoundary() and precision()
* Fix for inefficiency in StructureMap engine
* Update version of PubPack used by the IGPublisher
* Handle scope on TestScript R4 <-> r5 conversion
* Fix bug converting extension context = Resource (R4 <-> R5 conversion)
* Update VSAC importer for changes to VSAC FHIR authorization
* Fix broken links in profile comparison due to cross version issues

View File

@ -356,6 +356,6 @@ public class StructureDefinition30_40 {
}
static public boolean isResource300(String tn) {
return Utilities.existsInList(tn, "Account", "ActivityDefinition", "AllergyIntolerance", "AdverseEvent", "Appointment", "AppointmentResponse", "AuditEvent", "Basic", "Binary", "BodySite", "Bundle", "CapabilityStatement", "CarePlan", "CareTeam", "ChargeItem", "Claim", "ClaimResponse", "ClinicalImpression", "CodeSystem", "Communication", "CommunicationRequest", "CompartmentDefinition", "Composition", "ConceptMap", "Condition", "Consent", "Contract", "Coverage", "DataElement", "DetectedIssue", "Device", "DeviceComponent", "DeviceMetric", "DeviceRequest", "DeviceUseStatement", "DiagnosticReport", "DocumentManifest", "DocumentReference", "EligibilityRequest", "EligibilityResponse", "Encounter", "Endpoint", "EnrollmentRequest", "EnrollmentResponse", "EpisodeOfCare", "ExpansionProfile", "ExplanationOfBenefit", "FamilyMemberHistory", "Flag", "Goal", "GraphDefinition", "Group", "GuidanceResponse", "HealthcareService", "ImagingManifest", "ImagingStudy", "Immunization", "ImmunizationRecommendation", "ImplementationGuide", "Library", "Linkage", "List", "Location", "Measure", "MeasureReport", "Media", "Medication", "MedicationAdministration", "MedicationDispense", "MedicationRequest", "MedicationStatement", "MessageDefinition", "MessageHeader", "NamingSystem", "NutritionOrder", "Observation", "OperationDefinition", "OperationOutcome", "Organization", "Parameters", "Patient", "PaymentNotice", "PaymentReconciliation", "Person", "PlanDefinition", "Practitioner", "PractitionerRole", "Procedure", "ProcedureRequest", "ProcessRequest", "ProcessResponse", "Provenance", "Questionnaire", "QuestionnaireResponse", "ReferralRequest", "RelatedPerson", "RequestGroup", "ResearchStudy", "ResearchSubject", "RiskAssessment", "Schedule", "SearchParameter", "Sequence", "ServiceDefinition", "Slot", "Specimen", "StructureDefinition", "StructureMap", "Subscription", "Substance", "SupplyDelivery", "SupplyRequest", "Task", "TestScript", "TestReport", "ValueSet", "VisionPrescription");
return Utilities.existsInList(tn, "Resource", "DomainResource", "Account", "ActivityDefinition", "AllergyIntolerance", "AdverseEvent", "Appointment", "AppointmentResponse", "AuditEvent", "Basic", "Binary", "BodySite", "Bundle", "CapabilityStatement", "CarePlan", "CareTeam", "ChargeItem", "Claim", "ClaimResponse", "ClinicalImpression", "CodeSystem", "Communication", "CommunicationRequest", "CompartmentDefinition", "Composition", "ConceptMap", "Condition", "Consent", "Contract", "Coverage", "DataElement", "DetectedIssue", "Device", "DeviceComponent", "DeviceMetric", "DeviceRequest", "DeviceUseStatement", "DiagnosticReport", "DocumentManifest", "DocumentReference", "EligibilityRequest", "EligibilityResponse", "Encounter", "Endpoint", "EnrollmentRequest", "EnrollmentResponse", "EpisodeOfCare", "ExpansionProfile", "ExplanationOfBenefit", "FamilyMemberHistory", "Flag", "Goal", "GraphDefinition", "Group", "GuidanceResponse", "HealthcareService", "ImagingManifest", "ImagingStudy", "Immunization", "ImmunizationRecommendation", "ImplementationGuide", "Library", "Linkage", "List", "Location", "Measure", "MeasureReport", "Media", "Medication", "MedicationAdministration", "MedicationDispense", "MedicationRequest", "MedicationStatement", "MessageDefinition", "MessageHeader", "NamingSystem", "NutritionOrder", "Observation", "OperationDefinition", "OperationOutcome", "Organization", "Parameters", "Patient", "PaymentNotice", "PaymentReconciliation", "Person", "PlanDefinition", "Practitioner", "PractitionerRole", "Procedure", "ProcedureRequest", "ProcessRequest", "ProcessResponse", "Provenance", "Questionnaire", "QuestionnaireResponse", "ReferralRequest", "RelatedPerson", "RequestGroup", "ResearchStudy", "ResearchSubject", "RiskAssessment", "Schedule", "SearchParameter", "Sequence", "ServiceDefinition", "Slot", "Specimen", "StructureDefinition", "StructureMap", "Subscription", "Substance", "SupplyDelivery", "SupplyRequest", "Task", "TestScript", "TestReport", "ValueSet", "VisionPrescription");
}
}

View File

@ -22,16 +22,16 @@ public class VSACImporter extends OIDBasedValueSetImporter {
public static void main(String[] args) throws FHIRException, IOException, ParseException, URISyntaxException {
VSACImporter self = new VSACImporter();
self.process(args[0], args[1], args[2], args[3]);
self.process(args[0], args[1], args[2]);
}
private void process(String source, String dest, String username, String password) throws FHIRException, IOException, URISyntaxException {
private void process(String source, String dest, String apiKey) throws FHIRException, IOException, URISyntaxException {
CSVReader csv = new CSVReader(new FileInputStream(source));
csv.readHeaders();
FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", "fhir/vsac");
fhirToolingClient.setUsername(username);
fhirToolingClient.setPassword(password);
fhirToolingClient.setUsername("apikey");
fhirToolingClient.setPassword(apiKey);
int i = 0;
while (csv.line()) {

View File

@ -0,0 +1,47 @@
package org.hl7.fhir.convertors.conv30_40;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_40;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_40;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
public class Extension30_40Test {
private void convert(String filename) throws FHIRFormatError, IOException {
InputStream r3_input = this.getClass().getResourceAsStream("/"+filename);
org.hl7.fhir.dstu3.model.StructureDefinition r3_source = (org.hl7.fhir.dstu3.model.StructureDefinition) new org.hl7.fhir.dstu3.formats.JsonParser().parse(r3_input);
org.hl7.fhir.r4.model.Resource r4 = VersionConvertorFactory_30_40.convertResource(r3_source, new BaseAdvisor_30_40(false));
org.hl7.fhir.dstu3.model.StructureDefinition r3_roundtrip = (StructureDefinition) VersionConvertorFactory_30_40.convertResource(r4, new BaseAdvisor_30_40(false));
Assertions.assertTrue(r3_source.getContextType().equals(r3_roundtrip.getContextType()),
"Failed comparing " + r3_source.getContextType() + " and " + r3_roundtrip.getContextType());
}
@Test
public void testExtension_r3_res_base() throws IOException {
convert("extension_r3_res_base.json");
}
@Test
public void testExtension_r3_res_path() throws IOException {
convert("extension_r3_res_path.json");
}
@Test
public void testExtension_r3_dt_base() throws IOException {
convert("extension_r3_dt_base.json");
}
@Test
public void testExtension_r3_dt_path() throws IOException {
convert("extension_r3_dt_path.json");
}
}

View File

@ -0,0 +1,98 @@
{
"resourceType": "StructureDefinition",
"id": "coding-sctdescid",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
"valueCode": "fhir"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
"valueInteger": 1
}
],
"url": "http://hl7.org/fhir/StructureDefinition/coding-sctdescid",
"name": "sctdescid",
"status": "draft",
"date": "2015-02-28",
"publisher": "Health Level Seven, Inc. - FHIR Core WG",
"contact": [
{
"telecom": [
{
"system": "url",
"value": "http://hl7.org/special/committees/FHIR"
}
]
}
],
"description": "The SNOMED CT Description ID for the display.",
"fhirVersion": "3.0.2",
"mapping": [
{
"identity": "v2",
"uri": "http://hl7.org/v2",
"name": "HL7 v2 Mapping"
},
{
"identity": "rim",
"uri": "http://hl7.org/v3",
"name": "RIM Mapping"
}
],
"kind": "complex-type",
"abstract": false,
"contextType": "datatype",
"context": [
"Coding"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "SNOMED CT Description ID",
"definition": "The SNOMED CT Description ID for the display.",
"min": 0,
"max": "1",
"mapping": [
{
"identity": "v2",
"map": "N/A"
},
{
"identity": "rim",
"map": "n/a"
}
]
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"max": "0"
},
{
"id": "Extension.url",
"path": "Extension.url",
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://hl7.org/fhir/StructureDefinition/coding-sctdescid"
},
{
"id": "Extension.valueId",
"path": "Extension.valueId",
"type": [
{
"code": "id"
}
]
}
]
}
}

View File

@ -0,0 +1,106 @@
{
"resourceType": "StructureDefinition",
"id": "elementdefinition-equivalence",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
"valueCode": "fhir"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
"valueInteger": 1
}
],
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-equivalence",
"name": "equivalence",
"status": "draft",
"date": "2015-02-28",
"publisher": "Health Level Seven, Inc. - FHIR Core WG",
"contact": [
{
"telecom": [
{
"system": "url",
"value": "http://hl7.org/special/committees/FHIR"
}
]
}
],
"description": "The level of equivalence between the element containing the mapping and the element mapped to.",
"fhirVersion": "3.0.2",
"mapping": [
{
"identity": "rim",
"uri": "http://hl7.org/v3",
"name": "RIM Mapping"
}
],
"kind": "complex-type",
"abstract": false,
"contextType": "datatype",
"context": [
"ElementDefinition.mapping"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "equivalent | equal | wider | subsumes | narrower | specializes | inexact | unmatched | disjoint",
"definition": "The level of equivalence between the element containing the mapping and the element mapped to.",
"min": 0,
"max": "1",
"mapping": [
{
"identity": "rim",
"map": "N/A (MIF territory)"
}
]
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"max": "0"
},
{
"id": "Extension.url",
"path": "Extension.url",
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://hl7.org/fhir/StructureDefinition/elementdefinition-equivalence"
},
{
"id": "Extension.valueCode",
"path": "Extension.valueCode",
"type": [
{
"code": "code"
}
],
"binding": {
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName",
"valueString": "ConceptMapEquivalence"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding",
"valueBoolean": true
}
],
"strength": "required",
"description": "The degree of equivalence between concepts.",
"valueSetReference": {
"reference": "http://hl7.org/fhir/ValueSet/concept-map-equivalence"
}
}
}
]
}
}

View File

@ -0,0 +1,86 @@
{
"resourceType": "StructureDefinition",
"id": "capabilitystatement-supported-system",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
"valueCode": "fhir"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
"valueInteger": 1
}
],
"url": "http://hl7.org/fhir/StructureDefinition/capabilitystatement-supported-system",
"name": "supported-system",
"title": "Supported Code System",
"status": "draft",
"date": "2014-04-12",
"publisher": "Health Level Seven, Inc. - [WG Name] WG",
"contact": [
{
"telecom": [
{
"system": "url",
"value": "http://hl7.org/special/committees/fhir.htm"
}
]
}
],
"description": "A code system that is supported by the system that is not defined in a value set resource.",
"fhirVersion": "3.0.2",
"mapping": [
{
"identity": "rim",
"uri": "http://hl7.org/v3",
"name": "RIM Mapping"
}
],
"kind": "complex-type",
"abstract": false,
"contextType": "resource",
"context": [
"Resource"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Code system not defined in a value set",
"definition": "A code system that is supported by the system that is not defined in a value set resource.",
"comment": "Typically, this is a large terminology such as LOINC, SNOMED CT.",
"min": 0,
"max": "*",
"isModifier": false
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"max": "0"
},
{
"id": "Extension.url",
"path": "Extension.url",
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://hl7.org/fhir/StructureDefinition/capabilitystatement-supported-system"
},
{
"id": "Extension.valueUri",
"path": "Extension.valueUri",
"type": [
{
"code": "uri"
}
]
}
]
}
}

View File

@ -0,0 +1,86 @@
{
"resourceType": "StructureDefinition",
"id": "capabilitystatement-websocket",
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
"valueCode": "fhir"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
"valueInteger": 1
}
],
"url": "http://hl7.org/fhir/StructureDefinition/capabilitystatement-websocket",
"name": "websocket",
"title": "WebSocket",
"status": "draft",
"date": "2014-04-12",
"publisher": "Health Level Seven, Inc. - [WG Name] WG",
"contact": [
{
"telecom": [
{
"system": "url",
"value": "http://hl7.org/special/committees/fhir.htm"
}
]
}
],
"description": "Where the server provides its web socket end-point.",
"fhirVersion": "3.0.2",
"mapping": [
{
"identity": "rim",
"uri": "http://hl7.org/v3",
"name": "RIM Mapping"
}
],
"kind": "complex-type",
"abstract": false,
"contextType": "resource",
"context": [
"CapabilityStatement.rest"
],
"type": "Extension",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Extension",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Extension",
"path": "Extension",
"short": "Where server websocket end point is found",
"definition": "Where the server provides its web socket end-point.",
"comment": "Used for web-socket based subscriptions.",
"min": 0,
"max": "1",
"isModifier": false
},
{
"id": "Extension.extension",
"path": "Extension.extension",
"max": "0"
},
{
"id": "Extension.url",
"path": "Extension.url",
"type": [
{
"code": "uri"
}
],
"fixedUri": "http://hl7.org/fhir/StructureDefinition/capabilitystatement-websocket"
},
{
"id": "Extension.valueUri",
"path": "Extension.valueUri",
"type": [
{
"code": "uri"
}
]
}
]
}
}

View File

@ -196,7 +196,7 @@ public class ComparisonRenderer implements IEvaluationContext {
private void renderProfile(String id, ProfileComparison comp) throws IOException {
String template = templates.get("Profile");
Map<String, Base> vars = new HashMap<>();
ProfileComparer cs = new ProfileComparer(session, new ProfileUtilities(session.getContextLeft(), null, session.getPkp()), new ProfileUtilities(session.getContextRight(), null, session.getPkp()));
ProfileComparer cs = new ProfileComparer(session, new ProfileUtilities(session.getContextLeft(), null, session.getPkpLeft()), new ProfileUtilities(session.getContextRight(), null, session.getPkpRight()));
vars.put("left", new StringType(comp.getLeft().present()));
vars.put("right", new StringType(comp.getRight().present()));
vars.put("leftId", new StringType(comp.getLeft().getId()));

View File

@ -31,15 +31,17 @@ public class ComparisonSession {
private int count;
private boolean debug;
private String title;
private ProfileKnowledgeProvider pkp;
private ProfileKnowledgeProvider pkpLeft;
private ProfileKnowledgeProvider pkpRight;
public ComparisonSession(IWorkerContext contextLeft, IWorkerContext contextRight, String title, ProfileKnowledgeProvider pkp) {
public ComparisonSession(IWorkerContext contextLeft, IWorkerContext contextRight, String title, ProfileKnowledgeProvider pkpLeft, ProfileKnowledgeProvider pkpRight) {
super();
this.contextLeft = contextLeft;
this.contextRight = contextRight;
this.sessiondId = UUID.randomUUID().toString().toLowerCase();
this.title = title;
this.pkp = pkp;
this.pkpLeft = pkpLeft;
this.pkpRight = pkpRight;
// debug = true;
}
@ -88,7 +90,7 @@ public class ComparisonSession {
compares.put(key, csc);
return csc;
} else if (left instanceof StructureDefinition && right instanceof StructureDefinition) {
ProfileComparer cs = new ProfileComparer(this, new ProfileUtilities(contextLeft, null, pkp), new ProfileUtilities(contextRight, null, pkp));
ProfileComparer cs = new ProfileComparer(this, new ProfileUtilities(contextLeft, null, pkpLeft), new ProfileUtilities(contextRight, null, pkpRight));
ProfileComparison csc = cs.compare((StructureDefinition) left, (StructureDefinition) right);
compares.put(key, csc);
return csc;
@ -145,7 +147,11 @@ public class ComparisonSession {
return compares;
}
public ProfileKnowledgeProvider getPkp() {
return pkp;
public ProfileKnowledgeProvider getPkpLeft() {
return pkpLeft;
}
public ProfileKnowledgeProvider getPkpRight() {
return pkpRight;
}
}

View File

@ -983,12 +983,12 @@ public class ProfileComparer extends CanonicalResourceComparer {
String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
if (combined.hasLeft()) {
nc = utilsRight.genElementNameCell(gen, combined.getLeft().getDef(), "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
nc = utilsLeft.genElementNameCell(gen, combined.getLeft().getDef(), "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
} else {
nc = utilsRight.genElementNameCell(gen, combined.getRight().getDef(), "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
}
if (combined.hasLeft()) {
frame(utilsRight.genElementCells(gen, combined.getLeft().getDef(), "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, true, ext, used , ref, sName, nc, false, false, null), leftColor);
frame(utilsLeft.genElementCells(gen, combined.getLeft().getDef(), "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, true, ext, used , ref, sName, nc, false, false, null), leftColor);
} else {
frame(spacers(row, 4, gen), leftColor);
}

View File

@ -1435,7 +1435,10 @@ public class ProfileUtilities extends TranslatingUtilities {
}
ndc = differential.getElement().indexOf(diffMatches.get(i));
ndl = findEndOfElement(differential, ndc);
processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, null, redirector, srcSD);
ElementDefinition typeSliceElement = processPaths(indent+" ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, null, redirector, srcSD);
if (typeList.size() > start+1) {
typeSliceElement.setMin(0);
}
}
if (elementToRemove != null) {
differential.getElement().remove(elementToRemove);
@ -5445,9 +5448,10 @@ public class ProfileUtilities extends TranslatingUtilities {
private int prefixLength;
private String base;
private String name;
private String baseName;
private Set<String> errors = new HashSet<String>();
public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) {
this.inExtension = inExtension;
this.snapshot = snapshot;
this.prefixLength = prefixLength;
@ -5456,6 +5460,7 @@ public class ProfileUtilities extends TranslatingUtilities {
this.base = urlTail(base);
}
this.name = name;
this.baseName = baseName;
}
@Override
@ -5504,9 +5509,9 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (mandatory) {
if (prefixLength == 0)
errors.add("Differential contains path "+path+" which is not found in the in base "+name);
errors.add("Differential contains path "+path+" which is not found in the in base "+baseName);
else
errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+name);
errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName);
}
return 0;
}
@ -5565,7 +5570,7 @@ public class ProfileUtilities extends TranslatingUtilities {
processElementsIntoTree(edh, i, diff.getDifferential().getElement());
// now, we sort the siblings throughout the tree
ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType());
sortElements(edh, cmp, errors);
// now, we serialise them back to a list
@ -5652,27 +5657,27 @@ public class ProfileUtilities extends TranslatingUtilities {
if (profile==null) {
ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
} else {
ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present());
}
} else {
ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name);
}
} else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
if (profile==null)
ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
else
ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
} else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
if (profile==null)
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
} else if (child.getSelf().getType().size() == 1) {
StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
if (profile==null)
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
} else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
@ -5682,7 +5687,7 @@ public class ProfileUtilities extends TranslatingUtilities {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
if (sd == null)
throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId()));
ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present());
} else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
for (TypeRefComponent t: child.getSelf().getType()) {
if (!t.getWorkingCode().equals("Reference")) {
@ -5690,7 +5695,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
} else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
for (TypeRefComponent t: ed.getType()) {
if (!t.getWorkingCode().equals("Reference")) {
@ -5698,13 +5703,13 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
} else {
// this is allowed if we only profile the extensions
StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
if (profile==null)
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name);
ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present());
// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
}
return ccmp;

View File

@ -701,6 +701,9 @@ public class I18nConstants {
public static final String TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR = "TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR";
public static final String TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING = "TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING";
public static final String TX_SERVER_NO_BATCH_RESPONSE = "TX_SERVER_NO_BATCH_RESPONSE";
public static final String BUNDLE_POSSSIBLE_MATCHES = "BUNDLE_POSSSIBLE_MATCHES";
public static final String BUNDLE_BUNDLE_POSSIBLE_MATCH_NO_FU = "BUNDLE_BUNDLE_POSSIBLE_MATCH_NO_FU";
public static final String BUNDLE_BUNDLE_POSSIBLE_MATCH_WRONG_FU = "BUNDLE_BUNDLE_POSSIBLE_MATCH_WRONG_FU";
}

View File

@ -851,6 +851,9 @@ public class NpmPackage {
public InputStream loadExampleResource(String type, String id) throws IOException {
NpmPackageFolder f = folders.get("example");
if (f == null) {
f = folders.get("package/example");
}
if (f != null) {
JsonArray files = f.index.getAsJsonArray("files");
for (JsonElement e : files) {

View File

@ -108,7 +108,7 @@ public class BaseTestingUtilities {
}
public static String tempFolder(String name) throws IOException {
String path = ToolGlobalSettings.hasTempPath() ? ToolGlobalSettings.getTempPath() : Utilities.path("[tmp]", name);
String path = Utilities.path(ToolGlobalSettings.hasTempPath() ? ToolGlobalSettings.getTempPath() : "[tmp]", name);
Utilities.createDirectory(path);
return path;
}

View File

@ -111,7 +111,7 @@ Reference_REF_BadTargetType = Invalid Resource target type. Found {0}, but expec
Reference_REF_BadTargetType2 = The type ''{0}'' implied by the reference URL {1} is not a valid Target for this element (must be one of {2})
Reference_REF_CantMatchChoice = Unable to find a match for profile {0} among choices: {1}
Reference_REF_CantMatchType = Unable to find a match for profile {0} (by type) among choices: {1}
Reference_REF_CantResolve = Unable to resolve resource ''{0}''
Reference_REF_CantResolve = Unable to resolve resource with reference ''{0}''
Reference_REF_CantResolveProfile = Unable to resolve the profile reference ''{0}''
Reference_REF_Format1 = Relative URLs must be of the format [ResourceName]/[id], or a search URL is allowed ([type]?parameters. Encountered {0})
Reference_REF_Format2 = Relative URLs must be of the format [ResourceName]/[id]. Encountered {0}
@ -280,7 +280,7 @@ Unable_to_locate_the_profile__in_order_to_validate_against_it = Unable to locate
Reference__refers_to_a__not_a_ValueSet = Reference {0} refers to a {1} not a ValueSet
Not_done_yet_ValidatorHostServicesconformsToProfile_when_item_is_not_an_element = Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element
Not_supported_yet = Not supported yet
Unable_to_resolve_ = Unable to resolve {0}
Unable_to_resolve_ = Unable to resolve the reference {0}
Not_done_yet__resolve__locally_2 = Not done yet - resolve {0} locally (2)
Not_done_yet_ValidatorHostServicesexecuteFunction = Not done yet (ValidatorHostServices.executeFunction)
Not_done_yet_ValidatorHostServicescheckFunction = Not done yet (ValidatorHostServices.checkFunction)
@ -711,3 +711,6 @@ TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING = Base64 encoded values SHOULD not
SD_DERIVATION_KIND_MISMATCH = The structure definition constrains a kind of {0}, but has a different kind ({1})
VALUESET_IMPORT_UNION_INTERSECTION = This value set has a single include with multiple imported value sets. Per issue https://jira.hl7.org/browse/FHIR-25179, there has been confusion in the past whether these value sets are unioned or intersectioned. If this value set is contained in a package published prior to March 31 2022, it will be treated as a union, otherwise it will be treated as an intersection. If want a union, split the value set imports across multiple includes
TX_SERVER_NO_BATCH_RESPONSE = The server return null from a batch validation request
BUNDLE_POSSSIBLE_MATCHES = The bundle contains no match for {1} by the rules of Bundle reference resolution, but it has multiple resources that match {0} by resource type and id
BUNDLE_BUNDLE_POSSIBLE_MATCH_NO_FU = Entry {0} matches the reference {1} by type and id but it does not match the full target URL {2} by Bundle resolution rules
BUNDLE_BUNDLE_POSSIBLE_MATCH_WRONG_FU = Entry {0} matches the reference {1} by type and id but it''s fullUrl {2} does not match the full target URL {3} by Bundle resolution rules

View File

@ -950,8 +950,34 @@ public class BaseValidator implements IValidationContextResourceLoader {
if (match != null && resourceType != null)
rule(errors, IssueType.REQUIRED, -1, -1, path, match.getType().equals(resourceType), I18nConstants.REFERENCE_REF_RESOURCETYPE, ref, match.getType());
if (match == null)
if (match == null) {
warning(errors, IssueType.REQUIRED, -1, -1, path, !ref.startsWith("urn"), I18nConstants.BUNDLE_BUNDLE_NOT_LOCAL, ref);
if (!Utilities.isAbsoluteUrl(ref)) {
String[] p = ref.split("\\/");
List<Element> ml = new ArrayList<>();
if (p.length >= 2 && Utilities.existsInList(p[0], context.getResourceNames()) && Utilities.isValidId(p[1])) {
for (int i = 0; i < entries.size(); i++) {
Element we = entries.get(i);
Element r = we.getNamedChild(RESOURCE);
if (r != null && p[0].equals(r.fhirType()) && p[1].equals(r.getNamedChildValue("id")) ) {
ml.add(we);
}
}
}
if (ml.size() > 1) {
warning(errors, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_POSSSIBLE_MATCHES, ref, targetUrl);
}
for (Element e : ml) {
String fu = e.getChildValue(FULL_URL);
int i = entries.indexOf(e);
if (fu == null) {
warning(errors, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_BUNDLE_POSSIBLE_MATCH_NO_FU, i, ref, targetUrl);
} else {
warning(errors, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_BUNDLE_POSSIBLE_MATCH_WRONG_FU, i, ref, fu, targetUrl);
}
}
}
}
return match == null ? null : new IndexedElement(matchIndex, match, entries.get(matchIndex));
}

View File

@ -627,6 +627,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
igLoader.loadIg(getIgs(), getBinaries(), SHCParser.CURRENT_PACKAGE, true);
}
validator.setJurisdiction(jurisdiction);
validator.setLogProgress(true);
return validator;
}

View File

@ -60,7 +60,7 @@ public class ComparisonService {
public static void compareStructureDefinitions(String dest, ValidationEngine validator, String left, String right, StructureDefinition resLeft, StructureDefinition resRight) throws IOException, FHIRException, EOperationOutcome {
System.out.println("Comparing StructureDefinitions " + left + " to " + right);
ComparisonSession session = new ComparisonSession(validator.getContext(), validator.getContext(), "Comparing Profiles", null);
ComparisonSession session = new ComparisonSession(validator.getContext(), validator.getContext(), "Comparing Profiles", null, null);
session.compare(resLeft, resRight);
System.out.println("Generating output to " + dest + "...");

View File

@ -424,6 +424,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private QuestionnaireMode questionnaireMode;
private ValidationOptions baseOptions = new ValidationOptions();
private Map<String, CanonicalResourceLookupResult> crLookups = new HashMap<>();
private boolean logProgress;
public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) {
super(theContext, xverManager);
@ -2980,8 +2981,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (refType.equals("contained") || refType.equals("bundled")) {
pol = ReferenceValidationPolicy.CHECK_VALID;
} else {
if (policyAdvisor == null) pol = ReferenceValidationPolicy.IGNORE;
else pol = policyAdvisor.policyForReference(this, hostContext.getAppContext(), path, ref);
if (policyAdvisor == null) {
pol = ReferenceValidationPolicy.IGNORE;
} else {
pol = policyAdvisor.policyForReference(this, hostContext.getAppContext(), path, ref);
}
}
if (pol.checkExists()) {
@ -4397,7 +4401,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// this method is reentrant, but also the right place to tell the user what is going on if it's the root.
// if we're not at the root, we don't report progress
pctOwned = true;
pct = new PercentageTracker(resource.countDescendents()+1, resource.fhirType(), defn.getUrl());
pct = new PercentageTracker(resource.countDescendents()+1, resource.fhirType(), defn.getUrl(), logProgress);
}
if (BUNDLE.equals(element.fhirType())) {
if (debug) {
@ -4467,7 +4471,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
stack.resetIds();
if (pctOwned) {
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getUrl());
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getUrl(), logProgress);
}
startInner(hostContext, errors, resource, element, sd, stack, false, pct);
if (pctOwned) {
@ -4490,7 +4494,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
stack.resetIds();
if (pctOwned) {
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getUrl());
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getUrl(), logProgress);
}
startInner(hostContext, errors, resource, element, sd, stack, false, pct);
if (pctOwned) {
@ -6072,4 +6076,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return this;
}
public boolean isLogProgress() {
return logProgress;
}
public void setLogProgress(boolean logProgress) {
this.logProgress = logProgress;
}
}

View File

@ -7,19 +7,31 @@ public class PercentageTracker {
private int total;
private int last;
private int current;
private boolean log;
private String url;
private static int instance;
public PercentageTracker(int total, String fhirType, String url) {
public PercentageTracker(int total, String fhirType, String url, boolean log) {
this.total = total;
instance++;
last = 0;
this.log = log;
this.url = url;
if (log) {
System.out.print("Validate "+fhirType+" against "+url);
}
}
public void done() {
if (log) {
System.out.println("|");
}
}
public String getUrl() {
return url;
}
public void seeElement(Element e) {
if (e.getInstanceId() != instance) {
@ -28,14 +40,18 @@ public class PercentageTracker {
int pct = total == 0 ? 0: (current*100) / total;
if (pct > last + 2) {
while (last + 2 < pct) {
if (log) {
System.out.print(".");
}
last = last + 2;
if (last % 20 == 0) {
if (log) {
System.out.print(""+last);
}
}
}
}
}
}
}

View File

@ -132,7 +132,7 @@ public class ComparisonTests {
CanonicalResource left = load("left");
CanonicalResource right = load("right");
ComparisonSession session = new ComparisonSession(context, context, "Comparison Tests", null);
ComparisonSession session = new ComparisonSession(context, context, "Comparison Tests", null, null);
if (left instanceof CodeSystem && right instanceof CodeSystem) {
CodeSystemComparer cs = new CodeSystemComparer(session);

View File

@ -1292,3 +1292,43 @@ v: {
"system" : "http://loinc.org"
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"code" : "11502-2"
}, "url": "http://hl7.org/fhir/ValueSet/doc-typecodes--0", "version": "4.0.1", "lang":"null", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}####
v: {
"display" : "Laboratory report",
"code" : "11502-2",
"system" : "http://loinc.org"
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"code" : "11502-2"
}, "url": "http://hl7.org/fhir/ValueSet/doc-typecodes", "version": "4.0.1", "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"NO_MEMBERSHIP_CHECK", "versionFlexible":"false"}####
v: {
"display" : "Laboratory report",
"code" : "11502-2",
"system" : "http://loinc.org"
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"code" : "11502-2"
}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"true"}####
v: {
"display" : "Laboratory report",
"code" : "11502-2",
"system" : "http://loinc.org"
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"code" : "11502-2"
}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}####
v: {
"display" : "Laboratory report",
"code" : "11502-2",
"system" : "http://loinc.org"
}
-------------------------------------------------------------------------------------