Add support for validating against ImplementationGuide.global (#266)

* fix for new test case about profiles restricting types

* update release notes

* fix NPEs doing ProfileComparison

* fix non-translated messages in InstanceValidator

* Fix validation issues

* update to 1.1.22 tests

* fix bug finding children in getChildList when element is a reference to another element

* fixes to QuestionnaireRenderer + add QuestionnaireResponseRenderer

* Add more informative error when no type parser provided

* fix bug where current/dev builds do not properly update the cache

* improve test error message

* set up release notes

* fix bug with CDA snapshot generation

* fix bug in type checking code

* Add support for validating against ImplementationGuide.global

* Fix bug in QuestionnaireResponse rendering

* update tests dependency

* ping build

* Add additional StructureDefinition validation

* update release notes
This commit is contained in:
Grahame Grieve 2020-07-04 08:25:15 +10:00 committed by GitHub
parent 97f6758969
commit 92c02d2294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 294 additions and 18 deletions

View File

@ -0,0 +1,6 @@
* Added text/cql.identifier media type to Measure validation
* Fix bug in QuestionnaireResponse rendering
* Validate based on ImplementationGuide.global
* add validation parameter -crumb-trails
* improve validation of StructureDefinitions

View File

@ -1627,7 +1627,10 @@ public class ProfileUtilities extends TranslatingUtilities {
private boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types) { private boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types) {
while (sd != null) { while (sd != null) {
for (TypeRefComponent tr : types) { for (TypeRefComponent tr : types) {
if (sd.getType().equals(tr.getCode())) { if (sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && sd.getType().equals(tr.getCode())) {
return true;
}
if (sd.getUrl().equals(tr.getCode())) {
return true; return true;
} }
} }

View File

@ -7,6 +7,7 @@ import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent; import org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent;
@ -125,7 +126,7 @@ public class BundleRenderer extends ResourceRenderer {
} }
} }
private boolean allEntriesAreHistoryProvenance(List<BaseWrapper> entries) throws UnsupportedEncodingException, FHIRException, IOException { public static boolean allEntriesAreHistoryProvenance(List<BaseWrapper> entries) throws UnsupportedEncodingException, FHIRException, IOException {
for (BaseWrapper be : entries) { for (BaseWrapper be : entries) {
if (!"Provenance".equals(be.get("resource").fhirType())) { if (!"Provenance".equals(be.get("resource").fhirType())) {
return false; return false;
@ -134,6 +135,7 @@ public class BundleRenderer extends ResourceRenderer {
return !entries.isEmpty(); return !entries.isEmpty();
} }
private boolean allEntresAreHistoryProvenance(Bundle b) { private boolean allEntresAreHistoryProvenance(Bundle b) {
for (BundleEntryComponent be : b.getEntry()) { for (BundleEntryComponent be : b.getEntry()) {
if (!(be.getResource() instanceof Provenance)) { if (!(be.getResource() instanceof Provenance)) {

View File

@ -163,8 +163,8 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer {
} else { } else {
r.setIcon("icon-q-string.png", "Item"); r.setIcon("icon-q-string.png", "Item");
} }
String linkId = i.get("linkId").primitiveValue(); String linkId = i.has("linkId") ? i.get("linkId").primitiveValue() : "??";
String text = i.get("text").primitiveValue(); String text = i.has("text") ? i.get("text").primitiveValue() : "";
r.getCells().add(gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"#item."+linkId, linkId, null, null)); r.getCells().add(gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"#item."+linkId, linkId, null, null));
r.getCells().add(gen.new Cell(null, null, text, null, null)); r.getCells().add(gen.new Cell(null, null, text, null, null));
r.getCells().add(gen.new Cell(null, null, null, null, null)); r.getCells().add(gen.new Cell(null, null, null, null, null));

View File

@ -180,7 +180,14 @@ public interface IResourceValidator {
*/ */
public boolean isAllowExamples(); public boolean isAllowExamples();
public void setAllowExamples(boolean value) ; public void setAllowExamples(boolean value) ;
/**
* CrumbTrail - whether the validator creates hints to
* @return
*/
public boolean isCrumbTrails();
public void setCrumbTrails(boolean crumbTrails);
/** /**
* Validate suite * Validate suite

View File

@ -483,6 +483,7 @@ public class I18nConstants {
public static final String VALIDATION_VAL_PROFILE_OUTOFORDER = "Validation_VAL_Profile_OutOfOrder"; public static final String VALIDATION_VAL_PROFILE_OUTOFORDER = "Validation_VAL_Profile_OutOfOrder";
public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder"; public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder";
public static final String VALIDATION_VAL_PROFILE_UNKNOWN = "Validation_VAL_Profile_Unknown"; public static final String VALIDATION_VAL_PROFILE_UNKNOWN = "Validation_VAL_Profile_Unknown";
public static final String VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN = "VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN";
public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType";
public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2";
public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile"; public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile";
@ -517,4 +518,8 @@ public class I18nConstants {
public static final String TYPE_CHECKS_PATTERN_CC_US = "TYPE_CHECKS_PATTERN_CC_US"; public static final String TYPE_CHECKS_PATTERN_CC_US = "TYPE_CHECKS_PATTERN_CC_US";
public static final String TYPE_CHECKS_FIXED_CC = "TYPE_CHECKS_FIXED_CC"; public static final String TYPE_CHECKS_FIXED_CC = "TYPE_CHECKS_FIXED_CC";
public static final String TYPE_CHECKS_FIXED_CC_US = "TYPE_CHECKS_FIXED_CC_US"; public static final String TYPE_CHECKS_FIXED_CC_US = "TYPE_CHECKS_FIXED_CC_US";
} public static final String VALIDATION_VAL_PROFILE_SIGNPOST = "VALIDATION_VAL_PROFILE_SIGNPOST";
public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META";
public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL";
public static final String ERROR_GENERATING_SNAPSHOT = "ERROR_GENERATING_SNAPSHOT";
}

View File

@ -41,6 +41,7 @@ public class BaseTestingUtilities {
} }
} }
public static InputStream loadTestResourceStream(String... paths) throws IOException { public static InputStream loadTestResourceStream(String... paths) throws IOException {
String dir = System.getenv("FHIR-TEST-CASES"); String dir = System.getenv("FHIR-TEST-CASES");
if (dir != null && new File(dir).exists()) { if (dir != null && new File(dir).exists()) {

View File

@ -506,7 +506,7 @@ ALL_OK = All OK
SEARCHPARAMETER_NOTFOUND = Unable to find the base Search Parameter {0} so can't check that this SearchParameter is a proper derivation from it SEARCHPARAMETER_NOTFOUND = Unable to find the base Search Parameter {0} so can't check that this SearchParameter is a proper derivation from it
SEARCHPARAMETER_BASE_WRONG = The base {1} is not listed as a base in the derivedFrom SearchParameter SEARCHPARAMETER_BASE_WRONG = The base {1} is not listed as a base in the derivedFrom SearchParameter
SEARCHPARAMETER_TYPE_WRONG = The type {1} is different to the type {0} in the derivedFrom SearchParameter SEARCHPARAMETER_TYPE_WRONG = The type {1} is different to the type {0} in the derivedFrom SearchParameter
SEARCHPARAMETER_TYPE_WRONG = The expression "{1}" is different to the expression "{0}" in the derivedFrom SearchParameter, and this likely indicates that the derivation relationship is not valid SEARCHPARAMETER_EXP_WRONG = The expression "{2}" is not compatible with the expression "{1}" in the derivedFrom SearchParameter {0}, and this likely indicates that the derivation relationship is not valid
VALUESET_NO_SYSTEM_WARNING = No System specified, so Concepts and Filters can't be checked VALUESET_NO_SYSTEM_WARNING = No System specified, so Concepts and Filters can't be checked
VALUESET_INCLUDE_INVALID_CONCEPT_CODE = The code {1} is not valid in the system {0} VALUESET_INCLUDE_INVALID_CONCEPT_CODE = The code {1} is not valid in the system {0}
VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = The code {2} is not valid in the system {0} version {1} VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = The code {2} is not valid in the system {0} version {1}
@ -518,3 +518,8 @@ TYPE_CHECKS_PATTERN_CC_US = The pattern [system {0}, code {1}, display "{2}" and
TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4} TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4}
TYPE_CHECKS_FIXED_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4} TYPE_CHECKS_FIXED_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4}
Internal_error = Internal error: {0} Internal_error = Internal error: {0}
VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN = Global Profile reference "{0}" from IG {1} could not be resolved, so has not been checked
VALIDATION_VAL_PROFILE_SIGNPOST = Validate resource against profile {0}
VALIDATION_VAL_PROFILE_SIGNPOST_META = Validate resource against profile {0} - listed in meta
VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = Validate resource against profile {0} - a global profile in {1}
ERROR_GENERATING_SNAPSHOT = Error generating Snapshot: {0} (this usually arises from a problem in the differential)

View File

@ -346,7 +346,7 @@ public class BaseValidator {
* Set this parameter to <code>false</code> if the validation does not pass * Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation) * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/ */
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) { protected boolean ruleHtml(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
if (!thePass) { if (!thePass) {
msg = context.formatMessage(msg, null); msg = context.formatMessage(msg, null);
html = context.formatMessage(html, null); html = context.formatMessage(html, null);

View File

@ -264,7 +264,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
private boolean assumeValidRestReferences; private boolean assumeValidRestReferences;
private boolean noExtensibleBindingMessages; private boolean noExtensibleBindingMessages;
private boolean securityChecks; private boolean securityChecks;
private boolean crumbTrails;
private Locale locale; private Locale locale;
private List<ImplementationGuide> igs = new ArrayList<>();
private class AsteriskFilter implements FilenameFilter { private class AsteriskFilter implements FilenameFilter {
String dir; String dir;
@ -756,6 +758,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
context.cacheResource(r); context.cacheResource(r);
if (r instanceof ImplementationGuide) { if (r instanceof ImplementationGuide) {
canonical = ((ImplementationGuide) r).getUrl(); canonical = ((ImplementationGuide) r).getUrl();
igs.add((ImplementationGuide) r);
if (canonical.contains("/ImplementationGuide/")) { if (canonical.contains("/ImplementationGuide/")) {
Resource r2 = r.copy(); Resource r2 = r.copy();
((ImplementationGuide) r2).setUrl(canonical.substring(0, canonical.indexOf("/ImplementationGuide/"))); ((ImplementationGuide) r2).setUrl(canonical.substring(0, canonical.indexOf("/ImplementationGuide/")));
@ -765,8 +768,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
} }
} }
if (canonical != null) if (canonical != null) {
grabNatives(source, canonical); grabNatives(source, canonical);
}
} }
public Resource loadFileWithErrorChecking(String version, Entry<String, byte[]> t, String fn) { public Resource loadFileWithErrorChecking(String version, Entry<String, byte[]> t, String fn) {
@ -1311,8 +1315,10 @@ public class ValidationEngine implements IValidatorResourceFetcher {
validator.setAssumeValidRestReferences(assumeValidRestReferences); validator.setAssumeValidRestReferences(assumeValidRestReferences);
validator.setNoExtensibleWarnings(noExtensibleBindingMessages); validator.setNoExtensibleWarnings(noExtensibleBindingMessages);
validator.setSecurityChecks(securityChecks); validator.setSecurityChecks(securityChecks);
validator.setCrumbTrails(crumbTrails);
validator.getContext().setLocale(locale); validator.getContext().setLocale(locale);
validator.setFetcher(this); validator.setFetcher(this);
validator.getImplementationGuides().addAll(igs);
return validator; return validator;
} }
@ -1758,6 +1764,15 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.securityChecks = securityChecks; this.securityChecks = securityChecks;
} }
public boolean isCrumbTrails() {
return crumbTrails;
}
public void setCrumbTrails(boolean crumbTrails) {
this.crumbTrails = crumbTrails;
}
public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception { public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception {
Content cnt = loadContent(source, "validate"); Content cnt = loadContent(source, "validate");
org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);

View File

@ -65,6 +65,9 @@ public class CliContext {
@JsonProperty("securityChecks") @JsonProperty("securityChecks")
private boolean securityChecks = false; private boolean securityChecks = false;
@JsonProperty("crumbTrails")
private boolean crumbTrails = false;
@JsonProperty("locale") @JsonProperty("locale")
private String locale = Locale.ENGLISH.getDisplayLanguage(); private String locale = Locale.ENGLISH.getDisplayLanguage();
@ -425,6 +428,14 @@ public class CliContext {
return this; return this;
} }
public boolean isCrumbTrails() {
return crumbTrails;
}
public void setCrumbTrails(boolean crumbTrails) {
this.crumbTrails = crumbTrails;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

@ -194,6 +194,7 @@ public class ValidationService {
validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences()); validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences());
validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages()); validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
validator.setSecurityChecks(cliContext.isSecurityChecks()); validator.setSecurityChecks(cliContext.isSecurityChecks());
validator.setCrumbTrails(cliContext.isCrumbTrails());
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching()); TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
return validator; return validator;
} }

View File

@ -50,6 +50,7 @@ public class Params {
public static final String NO_INTERNAL_CACHING = "-no-internal-caching"; public static final String NO_INTERNAL_CACHING = "-no-internal-caching";
public static final String NO_EXTENSIBLE_BINDING_WARNINGS = "-no-extensible-binding-warnings"; public static final String NO_EXTENSIBLE_BINDING_WARNINGS = "-no-extensible-binding-warnings";
public static final String SECURITY_CHECKS = "-security-checks"; public static final String SECURITY_CHECKS = "-security-checks";
public static final String CRUMB_TRAIL = "-crumb-trails";
/** /**
* Checks the list of passed in params to see if it contains the passed in param. * Checks the list of passed in params to see if it contains the passed in param.
@ -153,7 +154,9 @@ public class Params {
} else if (args[i].equals(SNAPSHOT)) { } else if (args[i].equals(SNAPSHOT)) {
cliContext.setMode(Validator.EngineMode.SNAPSHOT); cliContext.setMode(Validator.EngineMode.SNAPSHOT);
} else if (args[i].equals(SECURITY_CHECKS)) { } else if (args[i].equals(SECURITY_CHECKS)) {
cliContext.setSecurityChecks(true); cliContext.setSecurityChecks(true);
} else if (args[i].equals(CRUMB_TRAIL)) {
cliContext.setCrumbTrails(true);
} else if (args[i].equals(SCAN)) { } else if (args[i].equals(SCAN)) {
cliContext.setMode(Validator.EngineMode.SCAN); cliContext.setMode(Validator.EngineMode.SCAN);
} else if (args[i].equals(TERMINOLOGY)) { } else if (args[i].equals(TERMINOLOGY)) {

View File

@ -104,6 +104,8 @@ import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.HumanName; import org.hl7.fhir.r5.model.HumanName;
import org.hl7.fhir.r5.model.Identifier; import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.ImplementationGuide.ImplementationGuideGlobalComponent;
import org.hl7.fhir.r5.model.InstantType; import org.hl7.fhir.r5.model.InstantType;
import org.hl7.fhir.r5.model.IntegerType; import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Period; import org.hl7.fhir.r5.model.Period;
@ -141,6 +143,7 @@ import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.MeasureValidator; import org.hl7.fhir.validation.instance.type.MeasureValidator;
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator; import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
import org.hl7.fhir.validation.instance.type.SearchParameterValidator; import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator;
import org.hl7.fhir.validation.instance.type.ValueSetValidator; import org.hl7.fhir.validation.instance.type.ValueSetValidator;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
@ -328,7 +331,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private BestPracticeWarningLevel bpWarnings; private BestPracticeWarningLevel bpWarnings;
private String validationLanguage; private String validationLanguage;
private boolean baseOnly; private boolean baseOnly;
private List<ImplementationGuide> igs = new ArrayList<>();
private List<String> extensionDomains = new ArrayList<String>(); private List<String> extensionDomains = new ArrayList<String>();
private IdStatus resourceIdRule; private IdStatus resourceIdRule;
@ -357,6 +361,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean allowExamples; private boolean allowExamples;
private boolean securityChecks; private boolean securityChecks;
private ProfileUtilities profileUtilities; private ProfileUtilities profileUtilities;
private boolean crumbTrails;
public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) { public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) {
super(theContext); super(theContext);
@ -445,6 +450,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
this.allowExamples = value; this.allowExamples = value;
} }
public boolean isCrumbTrails() {
return crumbTrails;
}
public void setCrumbTrails(boolean crumbTrails) {
this.crumbTrails = crumbTrails;
}
private boolean allowUnknownExtension(String url) { private boolean allowUnknownExtension(String url) {
if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression"))
@ -2716,6 +2728,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return extensionDomains; return extensionDomains;
} }
public List<ImplementationGuide> getImplementationGuides() {
return igs;
}
private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list) { private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list) {
for (TypeRefComponent tr : list) { for (TypeRefComponent tr : list) {
String url = tr.getWorkingCode(); String url = tr.getWorkingCode();
@ -3361,6 +3377,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
resolveBundleReferences(element, new ArrayList<Element>()); resolveBundleReferences(element, new ArrayList<Element>());
} }
startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials()); startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials());
hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, defn.getUrl());
Element meta = element.getNamedChild(META); Element meta = element.getNamedChild(META);
if (meta != null) { if (meta != null) {
@ -3371,6 +3388,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue()); StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue());
if (!defn.getUrl().equals(profile.primitiveValue())) { if (!defn.getUrl().equals(profile.primitiveValue())) {
if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) { if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) {
hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl());
stack.resetIds(); stack.resetIds();
startInner(hostContext, errors, resource, element, sd, stack, false); startInner(hostContext, errors, resource, element, sd, stack, false);
} }
@ -3378,6 +3396,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
i++; i++;
} }
} }
String rt = element.fhirType();
for (ImplementationGuide ig : igs) {
for (ImplementationGuideGlobalComponent gl : ig.getGlobal()) {
if (rt.equals(gl.getType())) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, gl.getProfile());
if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), sd != null, I18nConstants.VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN, gl.getProfile())) {
hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL, sd.getUrl(), ig.getUrl());
stack.resetIds();
startInner(hostContext, errors, resource, element, sd, stack, false);
}
}
}
}
} }
private void resolveBundleReferences(Element element, List<Element> bundles) { private void resolveBundleReferences(Element element, List<Element> bundles) {
@ -3481,7 +3512,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (element.getType().equals("CodeSystem")) { } else if (element.getType().equals("CodeSystem")) {
new CodeSystemValidator(context, timeTracker).validateCodeSystem(errors, element, stack); new CodeSystemValidator(context, timeTracker).validateCodeSystem(errors, element, stack);
} else if (element.getType().equals("SearchParameter")) { } else if (element.getType().equals("SearchParameter")) {
new SearchParameterValidator(context, timeTracker).validateSearchParameter(errors, element, stack); new SearchParameterValidator(context, timeTracker, fpe).validateSearchParameter(errors, element, stack);
} else if (element.getType().equals("StructureDefinition")) {
new StructureDefinitionValidator(context, timeTracker, fpe).validateStructureDefinition(errors, element, stack);
} else if (element.getType().equals("ValueSet")) { } else if (element.getType().equals("ValueSet")) {
new ValueSetValidator(context, timeTracker).validateValueSet(errors, element, stack); new ValueSetValidator(context, timeTracker).validateValueSet(errors, element, stack);
} }

View File

@ -192,7 +192,7 @@ public class BundleValidator extends BaseValidator{
boolean ok = bundle.hasChild(META) boolean ok = bundle.hasChild(META)
&& bundle.getNamedChild(META).hasChild(LAST_UPDATED) && bundle.getNamedChild(META).hasChild(LAST_UPDATED)
&& bundle.getNamedChild(META).getNamedChild(LAST_UPDATED).hasValue(); && bundle.getNamedChild(META).getNamedChild(LAST_UPDATED).hasValue();
rule(errors, IssueType.REQUIRED, stack.getLiteralPath(), ok, I18nConstants.DOCUMENT_DATE_REQUIRED, I18nConstants.DOCUMENT_DATE_REQUIRED_HTML); ruleHtml(errors, IssueType.REQUIRED, stack.getLiteralPath(), ok, I18nConstants.DOCUMENT_DATE_REQUIRED, I18nConstants.DOCUMENT_DATE_REQUIRED_HTML);
} }
private void checkAllInterlinked(List<ValidationMessage> errors, List<Element> entries, NodeStack stack, Element bundle, boolean isError) { private void checkAllInterlinked(List<ValidationMessage> errors, List<Element> entries, NodeStack stack, Element bundle, boolean isError) {

View File

@ -1,12 +1,19 @@
package org.hl7.fhir.validation.instance.type; package org.hl7.fhir.validation.instance.type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.ExpressionNode.Kind;
import org.hl7.fhir.r5.model.ExpressionNode.Operation;
import org.hl7.fhir.r5.model.SearchParameter; import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -14,13 +21,26 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source; import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.validation.BaseValidator; import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker; import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.type.SearchParameterValidator.FhirPathSorter;
import org.hl7.fhir.validation.instance.utils.NodeStack; import org.hl7.fhir.validation.instance.utils.NodeStack;
public class SearchParameterValidator extends BaseValidator { public class SearchParameterValidator extends BaseValidator {
public SearchParameterValidator(IWorkerContext context, TimeTracker timeTracker) { public class FhirPathSorter implements Comparator<ExpressionNode> {
@Override
public int compare(ExpressionNode arg0, ExpressionNode arg1) {
return arg0.toString().compareTo(arg1.toString());
}
}
private FHIRPathEngine fpe;
public SearchParameterValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe) {
super(context); super(context);
source = Source.InstanceValidator; source = Source.InstanceValidator;
this.fpe = fpe;
this.timeTracker = timeTracker; this.timeTracker = timeTracker;
} }
@ -37,10 +57,49 @@ public class SearchParameterValidator extends BaseValidator {
rule(errors, IssueType.BUSINESSRULE,stack.getLiteralPath(), sp.hasBase(b.primitiveValue()), I18nConstants.SEARCHPARAMETER_BASE_WRONG, master, b.primitiveValue()); rule(errors, IssueType.BUSINESSRULE,stack.getLiteralPath(), sp.hasBase(b.primitiveValue()), I18nConstants.SEARCHPARAMETER_BASE_WRONG, master, b.primitiveValue());
} }
rule(errors, IssueType.BUSINESSRULE,stack.getLiteralPath(), !cs.hasChild("type") || sp.getType().toCode().equals(cs.getNamedChildValue("type")), I18nConstants.SEARCHPARAMETER_TYPE_WRONG, master, sp.getType().toCode(), cs.getNamedChildValue("type")); rule(errors, IssueType.BUSINESSRULE,stack.getLiteralPath(), !cs.hasChild("type") || sp.getType().toCode().equals(cs.getNamedChildValue("type")), I18nConstants.SEARCHPARAMETER_TYPE_WRONG, master, sp.getType().toCode(), cs.getNamedChildValue("type"));
warning(errors, IssueType.BUSINESSRULE,stack.getLiteralPath(), !cs.hasChild("expression") || sp.getExpression().equals(cs.getNamedChildValue("expression")), I18nConstants.SEARCHPARAMETER_EXP_WRONG, master, sp.getExpression(), cs.getNamedChildValue("expression")); if (cs.hasChild("expression") && !sp.getExpression().equals(cs.getNamedChildValue("expression"))) {
// todo: cjeck compositions List<String> bases = new ArrayList<>();
for (Element b : cs.getChildren("base")) {
bases.add(b.primitiveValue());
}
String expThis = canonicalise(cs.getNamedChildValue("expression"), bases);
String expOther = canonicalise(sp.getExpression(), bases);
warning(errors, IssueType.BUSINESSRULE,stack.getLiteralPath(), expThis.equals(expOther), I18nConstants.SEARCHPARAMETER_EXP_WRONG, master, sp.getExpression(), cs.getNamedChildValue("expression"));
}
// todo: check compositions
} }
} }
} }
private String canonicalise(String path, List<String> bases) {
ExpressionNode exp = fpe.parse(path);
List<ExpressionNode> pass = new ArrayList<>();
while (exp != null) {
if ((exp.getKind() != Kind.Name && !(exp.getKind() == Kind.Group && exp.getGroup().getKind() == Kind.Name))) {
return path;
}
if (exp.getOperation() != null && exp.getOperation() != Operation.Union) {
return path;
}
ExpressionNode nexp = exp.getOpNext();
exp.setOperation(null);
exp.setOpNext(null);
String name = exp.getKind() == Kind.Name ? exp.getName() : exp.getGroup().getName();
if (context.getResourceNames().contains(name)) {
if (bases.contains(name)) {
pass.add(exp);
}
} else {
pass.add(exp);
}
exp = nexp;
}
Collections.sort(pass, new FhirPathSorter());
for (int i = 0; i < pass.size()-1; i++) {
pass.get(i).setOperation(Operation.Union);
pass.get(i).setOpNext(pass.get(i+1));
}
return pass.get(0).toString();
}
} }

View File

@ -0,0 +1,115 @@
package org.hl7.fhir.validation.instance.type;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.convertors.VersionConvertor_10_50;
import org.hl7.fhir.convertors.VersionConvertor_14_50;
import org.hl7.fhir.convertors.VersionConvertor_30_50;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.ExpressionNode.Kind;
import org.hl7.fhir.r5.model.ExpressionNode.Operation;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator.FhirPathSorter;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class StructureDefinitionValidator extends BaseValidator {
public class FhirPathSorter implements Comparator<ExpressionNode> {
@Override
public int compare(ExpressionNode arg0, ExpressionNode arg1) {
return arg0.toString().compareTo(arg1.toString());
}
}
private FHIRPathEngine fpe;
public StructureDefinitionValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe) {
super(context);
source = Source.InstanceValidator;
this.fpe = fpe;
this.timeTracker = timeTracker;
}
public void validateStructureDefinition(List<ValidationMessage> errors, Element src, NodeStack stack) {
StructureDefinition sd;
try {
sd = loadAsSD(src);
List<ElementDefinition> snapshot = sd.getSnapshot().getElement();
sd.setSnapshot(null);
StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
if (warning(errors, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) {
List<ValidationMessage> msgs = new ArrayList<>();
ProfileUtilities pu = new ProfileUtilities(context, msgs, null);
pu.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir", sd.getName());
if (msgs.size() > 0) {
for (ValidationMessage msg : msgs) {
// we need to set the location for the context
String loc = msg.getLocation();
if (loc.contains("#")) {
msg.setLocation(stack.getLiteralPath()+".differential.element.where(path = '"+loc.substring(loc.indexOf("#")+1)+"')");
} else {
msg.setLocation(stack.getLiteralPath());
}
errors.add(msg);
}
}
}
if (!snapshot.isEmpty()) {
System.out.print("?");
}
} catch (FHIRException | IOException e) {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage());
}
}
private StructureDefinition loadAsSD(Element src) throws FHIRException, IOException {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
Manager.compose(context, src, bs, FhirFormat.JSON, OutputStyle.NORMAL, null);
if (VersionUtilities.isR2Ver(context.getVersion())) {
org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(bs.toByteArray());
return (StructureDefinition) VersionConvertor_10_50.convertResource(r2);
}
if (VersionUtilities.isR2BVer(context.getVersion())) {
org.hl7.fhir.dstu2016may.model.Resource r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(bs.toByteArray());
return (StructureDefinition) VersionConvertor_14_50.convertResource(r2b);
}
if (VersionUtilities.isR3Ver(context.getVersion())) {
org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(bs.toByteArray());
return (StructureDefinition) VersionConvertor_30_50.convertResource(r3, false);
}
if (VersionUtilities.isR4Ver(context.getVersion())) {
org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(bs.toByteArray());
return (StructureDefinition) VersionConvertor_40_50.convertResource(r4);
}
return (StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(bs.toByteArray());
}
}

View File

@ -72,7 +72,7 @@ public class ProfileValidator extends BaseValidator {
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) { protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
String rn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path; String rn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
return super.rule(errors, type, path, b, msg, "<a href=\""+(rn.toLowerCase())+".html\">"+rn+"</a>: "+Utilities.escapeXml(msg)); return super.ruleHtml(errors, type, path, b, msg, "<a href=\""+(rn.toLowerCase())+".html\">"+rn+"</a>: "+Utilities.escapeXml(msg));
} }
public List<ValidationMessage> validate(StructureDefinition profile, boolean forBuild) { public List<ValidationMessage> validate(StructureDefinition profile, boolean forBuild) {

View File

@ -26,6 +26,7 @@ import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.FhirPublication; import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
@ -169,12 +170,18 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
vCurr.loadIg(e.getAsString(), true); vCurr.loadIg(e.getAsString(), true);
} }
} }
if (content.has("crumb-trail")) {
val.setCrumbTrails(content.get("crumb-trail").getAsBoolean());
}
if (content.has("supporting")) { if (content.has("supporting")) {
for (JsonElement e : content.getAsJsonArray("supporting")) { for (JsonElement e : content.getAsJsonArray("supporting")) {
String filename = e.getAsString(); String filename = e.getAsString();
String contents = TestingUtilities.loadTestResource("validator", filename); String contents = TestingUtilities.loadTestResource("validator", filename);
CanonicalResource mr = (CanonicalResource) loadResource(filename, contents); CanonicalResource mr = (CanonicalResource) loadResource(filename, contents);
val.getContext().cacheResource(mr); val.getContext().cacheResource(mr);
if (mr instanceof ImplementationGuide) {
val.getImplementationGuides().add((ImplementationGuide) mr);
}
} }
} }
if (content.has("profiles")) { if (content.has("profiles")) {
@ -219,6 +226,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
String contents = TestingUtilities.loadTestResource("validator", filename); String contents = TestingUtilities.loadTestResource("validator", filename);
CanonicalResource mr = (CanonicalResource) loadResource(filename, contents); CanonicalResource mr = (CanonicalResource) loadResource(filename, contents);
val.getContext().cacheResource(mr); val.getContext().cacheResource(mr);
if (mr instanceof ImplementationGuide) {
val.getImplementationGuides().add((ImplementationGuide) mr);
}
} }
} }
String filename = profile.get("source").getAsString(); String filename = profile.get("source").getAsString();

View File

@ -17,7 +17,7 @@
<properties> <properties>
<hapi_fhir_version>5.0.0</hapi_fhir_version> <hapi_fhir_version>5.0.0</hapi_fhir_version>
<validator_test_case_version>1.1.22</validator_test_case_version> <validator_test_case_version>1.1.23</validator_test_case_version>
<junit_jupiter_version>5.6.2</junit_jupiter_version> <junit_jupiter_version>5.6.2</junit_jupiter_version>
<maven_surefire_version>3.0.0-M4</maven_surefire_version> <maven_surefire_version>3.0.0-M4</maven_surefire_version>
<jacoco_version>0.8.5</jacoco_version> <jacoco_version>0.8.5</jacoco_version>