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:
parent
97f6758969
commit
92c02d2294
|
@ -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
|
||||
|
|
@ -1627,7 +1627,10 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
private boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types) {
|
||||
while (sd != null) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import java.util.List;
|
|||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
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.BundleEntryComponent;
|
||||
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) {
|
||||
if (!"Provenance".equals(be.get("resource").fhirType())) {
|
||||
return false;
|
||||
|
@ -134,6 +135,7 @@ public class BundleRenderer extends ResourceRenderer {
|
|||
return !entries.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
private boolean allEntresAreHistoryProvenance(Bundle b) {
|
||||
for (BundleEntryComponent be : b.getEntry()) {
|
||||
if (!(be.getResource() instanceof Provenance)) {
|
||||
|
|
|
@ -163,8 +163,8 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer {
|
|||
} else {
|
||||
r.setIcon("icon-q-string.png", "Item");
|
||||
}
|
||||
String linkId = i.get("linkId").primitiveValue();
|
||||
String text = i.get("text").primitiveValue();
|
||||
String linkId = i.has("linkId") ? i.get("linkId").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, null, text, null, null));
|
||||
r.getCells().add(gen.new Cell(null, null, null, null, null));
|
||||
|
|
|
@ -181,6 +181,13 @@ public interface IResourceValidator {
|
|||
public boolean isAllowExamples();
|
||||
public void setAllowExamples(boolean value) ;
|
||||
|
||||
/**
|
||||
* CrumbTrail - whether the validator creates hints to
|
||||
* @return
|
||||
*/
|
||||
public boolean isCrumbTrails();
|
||||
public void setCrumbTrails(boolean crumbTrails);
|
||||
|
||||
|
||||
/**
|
||||
* Validate suite
|
||||
|
|
|
@ -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_SLICEORDER = "Validation_VAL_Profile_SliceOrder";
|
||||
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_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2";
|
||||
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_FIXED_CC = "TYPE_CHECKS_FIXED_CC";
|
||||
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";
|
||||
}
|
|
@ -41,6 +41,7 @@ public class BaseTestingUtilities {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static InputStream loadTestResourceStream(String... paths) throws IOException {
|
||||
String dir = System.getenv("FHIR-TEST-CASES");
|
||||
if (dir != null && new File(dir).exists()) {
|
||||
|
|
|
@ -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_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 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_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}
|
||||
|
@ -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_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}
|
||||
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)
|
||||
|
|
|
@ -346,7 +346,7 @@ public class BaseValidator {
|
|||
* 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)
|
||||
*/
|
||||
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) {
|
||||
msg = context.formatMessage(msg, null);
|
||||
html = context.formatMessage(html, null);
|
||||
|
|
|
@ -264,7 +264,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
|||
private boolean assumeValidRestReferences;
|
||||
private boolean noExtensibleBindingMessages;
|
||||
private boolean securityChecks;
|
||||
private boolean crumbTrails;
|
||||
private Locale locale;
|
||||
private List<ImplementationGuide> igs = new ArrayList<>();
|
||||
|
||||
private class AsteriskFilter implements FilenameFilter {
|
||||
String dir;
|
||||
|
@ -756,6 +758,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
|||
context.cacheResource(r);
|
||||
if (r instanceof ImplementationGuide) {
|
||||
canonical = ((ImplementationGuide) r).getUrl();
|
||||
igs.add((ImplementationGuide) r);
|
||||
if (canonical.contains("/ImplementationGuide/")) {
|
||||
Resource r2 = r.copy();
|
||||
((ImplementationGuide) r2).setUrl(canonical.substring(0, canonical.indexOf("/ImplementationGuide/")));
|
||||
|
@ -765,9 +768,10 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (canonical != null)
|
||||
if (canonical != null) {
|
||||
grabNatives(source, canonical);
|
||||
}
|
||||
}
|
||||
|
||||
public Resource loadFileWithErrorChecking(String version, Entry<String, byte[]> t, String fn) {
|
||||
if (debug)
|
||||
|
@ -1311,8 +1315,10 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
|||
validator.setAssumeValidRestReferences(assumeValidRestReferences);
|
||||
validator.setNoExtensibleWarnings(noExtensibleBindingMessages);
|
||||
validator.setSecurityChecks(securityChecks);
|
||||
validator.setCrumbTrails(crumbTrails);
|
||||
validator.getContext().setLocale(locale);
|
||||
validator.setFetcher(this);
|
||||
validator.getImplementationGuides().addAll(igs);
|
||||
return validator;
|
||||
}
|
||||
|
||||
|
@ -1758,6 +1764,15 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
|||
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 {
|
||||
Content cnt = loadContent(source, "validate");
|
||||
org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
|
||||
|
|
|
@ -65,6 +65,9 @@ public class CliContext {
|
|||
@JsonProperty("securityChecks")
|
||||
private boolean securityChecks = false;
|
||||
|
||||
@JsonProperty("crumbTrails")
|
||||
private boolean crumbTrails = false;
|
||||
|
||||
@JsonProperty("locale")
|
||||
private String locale = Locale.ENGLISH.getDisplayLanguage();
|
||||
|
||||
|
@ -425,6 +428,14 @@ public class CliContext {
|
|||
return this;
|
||||
}
|
||||
|
||||
public boolean isCrumbTrails() {
|
||||
return crumbTrails;
|
||||
}
|
||||
|
||||
public void setCrumbTrails(boolean crumbTrails) {
|
||||
this.crumbTrails = crumbTrails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -194,6 +194,7 @@ public class ValidationService {
|
|||
validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences());
|
||||
validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
|
||||
validator.setSecurityChecks(cliContext.isSecurityChecks());
|
||||
validator.setCrumbTrails(cliContext.isCrumbTrails());
|
||||
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
|
||||
return validator;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public class Params {
|
|||
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 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.
|
||||
|
@ -154,6 +155,8 @@ public class Params {
|
|||
cliContext.setMode(Validator.EngineMode.SNAPSHOT);
|
||||
} else if (args[i].equals(SECURITY_CHECKS)) {
|
||||
cliContext.setSecurityChecks(true);
|
||||
} else if (args[i].equals(CRUMB_TRAIL)) {
|
||||
cliContext.setCrumbTrails(true);
|
||||
} else if (args[i].equals(SCAN)) {
|
||||
cliContext.setMode(Validator.EngineMode.SCAN);
|
||||
} else if (args[i].equals(TERMINOLOGY)) {
|
||||
|
|
|
@ -104,6 +104,8 @@ import org.hl7.fhir.r5.model.ExpressionNode;
|
|||
import org.hl7.fhir.r5.model.Extension;
|
||||
import org.hl7.fhir.r5.model.HumanName;
|
||||
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.IntegerType;
|
||||
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.QuestionnaireValidator;
|
||||
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.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
@ -329,6 +332,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
private String validationLanguage;
|
||||
private boolean baseOnly;
|
||||
|
||||
private List<ImplementationGuide> igs = new ArrayList<>();
|
||||
private List<String> extensionDomains = new ArrayList<String>();
|
||||
|
||||
private IdStatus resourceIdRule;
|
||||
|
@ -357,6 +361,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
private boolean allowExamples;
|
||||
private boolean securityChecks;
|
||||
private ProfileUtilities profileUtilities;
|
||||
private boolean crumbTrails;
|
||||
|
||||
public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) {
|
||||
super(theContext);
|
||||
|
@ -445,6 +450,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
this.allowExamples = value;
|
||||
}
|
||||
|
||||
public boolean isCrumbTrails() {
|
||||
return crumbTrails;
|
||||
}
|
||||
|
||||
public void setCrumbTrails(boolean crumbTrails) {
|
||||
this.crumbTrails = crumbTrails;
|
||||
}
|
||||
|
||||
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"))
|
||||
|
@ -2716,6 +2728,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
return extensionDomains;
|
||||
}
|
||||
|
||||
public List<ImplementationGuide> getImplementationGuides() {
|
||||
return igs;
|
||||
}
|
||||
|
||||
private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list) {
|
||||
for (TypeRefComponent tr : list) {
|
||||
String url = tr.getWorkingCode();
|
||||
|
@ -3361,6 +3377,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
resolveBundleReferences(element, new ArrayList<Element>());
|
||||
}
|
||||
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);
|
||||
if (meta != null) {
|
||||
|
@ -3371,6 +3388,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
StructureDefinition sd = context.fetchResource(StructureDefinition.class, 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())) {
|
||||
hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl());
|
||||
stack.resetIds();
|
||||
startInner(hostContext, errors, resource, element, sd, stack, false);
|
||||
}
|
||||
|
@ -3378,6 +3396,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
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) {
|
||||
|
@ -3481,7 +3512,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
} else if (element.getType().equals("CodeSystem")) {
|
||||
new CodeSystemValidator(context, timeTracker).validateCodeSystem(errors, element, stack);
|
||||
} 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")) {
|
||||
new ValueSetValidator(context, timeTracker).validateValueSet(errors, element, stack);
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ public class BundleValidator extends BaseValidator{
|
|||
boolean ok = bundle.hasChild(META)
|
||||
&& bundle.getNamedChild(META).hasChild(LAST_UPDATED)
|
||||
&& 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) {
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package org.hl7.fhir.validation.instance.type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
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.ValueSet;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
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.validation.BaseValidator;
|
||||
import org.hl7.fhir.validation.TimeTracker;
|
||||
import org.hl7.fhir.validation.instance.type.SearchParameterValidator.FhirPathSorter;
|
||||
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
||||
|
||||
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);
|
||||
source = Source.InstanceValidator;
|
||||
this.fpe = fpe;
|
||||
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(), !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"));
|
||||
// todo: cjeck compositions
|
||||
if (cs.hasChild("expression") && !sp.getExpression().equals(cs.getNamedChildValue("expression"))) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -72,7 +72,7 @@ public class ProfileValidator extends BaseValidator {
|
|||
|
||||
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
|
||||
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) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.hl7.fhir.r5.model.Base;
|
|||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.Constants;
|
||||
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.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
|
@ -169,12 +170,18 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
|
|||
vCurr.loadIg(e.getAsString(), true);
|
||||
}
|
||||
}
|
||||
if (content.has("crumb-trail")) {
|
||||
val.setCrumbTrails(content.get("crumb-trail").getAsBoolean());
|
||||
}
|
||||
if (content.has("supporting")) {
|
||||
for (JsonElement e : content.getAsJsonArray("supporting")) {
|
||||
String filename = e.getAsString();
|
||||
String contents = TestingUtilities.loadTestResource("validator", filename);
|
||||
CanonicalResource mr = (CanonicalResource) loadResource(filename, contents);
|
||||
val.getContext().cacheResource(mr);
|
||||
if (mr instanceof ImplementationGuide) {
|
||||
val.getImplementationGuides().add((ImplementationGuide) mr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (content.has("profiles")) {
|
||||
|
@ -219,6 +226,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
|
|||
String contents = TestingUtilities.loadTestResource("validator", filename);
|
||||
CanonicalResource mr = (CanonicalResource) loadResource(filename, contents);
|
||||
val.getContext().cacheResource(mr);
|
||||
if (mr instanceof ImplementationGuide) {
|
||||
val.getImplementationGuides().add((ImplementationGuide) mr);
|
||||
}
|
||||
}
|
||||
}
|
||||
String filename = profile.get("source").getAsString();
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -17,7 +17,7 @@
|
|||
|
||||
<properties>
|
||||
<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>
|
||||
<maven_surefire_version>3.0.0-M4</maven_surefire_version>
|
||||
<jacoco_version>0.8.5</jacoco_version>
|
||||
|
|
Loading…
Reference in New Issue