Fix bug validating mime types without terminology serverBase (warning, not error)

Performance improvements in JSON metadata based parser
Add first round of supplement validation
improve error message on profile validation fail
fix NPE validating some slices
fix bug validating canonicals as part of choice data types
Adds special support for http://hl7.org/fhirpath/System.* types
fix bug matching slices in contained resources that have references to #
This commit is contained in:
Grahame Grieve 2021-02-08 09:40:25 +11:00
parent 2413ec1dbb
commit ef3b8c1f0a
13 changed files with 137 additions and 47 deletions

View File

@ -750,7 +750,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
// 3rd pass: hit the server // 3rd pass: hit the server
for (CodingValidationRequest t : codes) { for (CodingValidationRequest t : codes) {
t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs) : null); t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs) : null);
codeSystemsUsed.add(t.getCoding().getSystem()); if (t.getCoding().hasSystem()) {
codeSystemsUsed.add(t.getCoding().getSystem());
}
if (txCache != null) { if (txCache != null) {
t.setResult(txCache.getValidation(t.getCacheToken())); t.setResult(txCache.getValidation(t.getCacheToken()));
} }
@ -846,7 +848,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
options = ValidationOptions.defaults(); options = ValidationOptions.defaults();
} }
codeSystemsUsed.add(code.getSystem()); if (code.hasSystem()) {
codeSystemsUsed.add(code.getSystem());
}
CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null; CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null;
ValidationResult res = null; ValidationResult res = null;
if (txCache != null) { if (txCache != null) {
@ -922,7 +926,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return res; return res;
} }
for (Coding c : code.getCoding()) { for (Coding c : code.getCoding()) {
codeSystemsUsed.add(c.getSystem()); if (c.hasSystem()) {
codeSystemsUsed.add(c.getSystem());
}
} }
if (options.isUseClient()) { if (options.isUseClient()) {
@ -1134,6 +1140,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return noTerminologyServer; return noTerminologyServer;
} }
public void setNoTerminologyServer(boolean noTerminologyServer) {
this.noTerminologyServer = noTerminologyServer;
}
public String getName() { public String getName() {
return name; return name;
} }

View File

@ -76,15 +76,19 @@ public class JsonParser extends ParserBase {
private Map<JsonElement, LocationData> map; private Map<JsonElement, LocationData> map;
private boolean allowComments; private boolean allowComments;
private FHIRPathEngine fpe;
private ProfileUtilities profileUtilities; private ProfileUtilities profileUtilities;
public JsonParser(IWorkerContext context) { public JsonParser(IWorkerContext context, ProfileUtilities utilities) {
super(context); super(context);
this.fpe = new FHIRPathEngine(this.context); this.profileUtilities = utilities;
this.profileUtilities = new ProfileUtilities(this.context, null, null, this.fpe); }
}
public JsonParser(IWorkerContext context) {
super(context);
this.profileUtilities = new ProfileUtilities(this.context, null, null, new FHIRPathEngine(context));
}
public Element parse(String source, String type) throws Exception { public Element parse(String source, String type) throws Exception {
JsonObject obj = (JsonObject) new com.google.gson.JsonParser().parse(source); JsonObject obj = (JsonObject) new com.google.gson.JsonParser().parse(source);

View File

@ -152,6 +152,9 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
throw new FHIRException(warningMessage); throw new FHIRException(warningMessage);
} }
} }
if (cs != null && cs.hasSupplements()) {
return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getUrl()));
}
if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) { if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) {
warningMessage = "Resolved system "+system+", but the definition is not complete"; warningMessage = "Resolved system "+system+", but the definition is not complete";
if (!inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment if (!inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment

View File

@ -304,7 +304,6 @@ public class FHIRPathEngine {
public ValueSet resolveValueSet(Object appContext, String url); public ValueSet resolveValueSet(Object appContext, String url);
} }
/** /**
* @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined) * @param worker - used when validating paths (@check), and used doing value set membership when executing tests (once that's defined)
*/ */

View File

@ -50,6 +50,9 @@ public class I18nConstants {
public static final String CODESYSTEM_CS_VS_INVALID = "CodeSystem_CS_VS_Invalid"; public static final String CODESYSTEM_CS_VS_INVALID = "CodeSystem_CS_VS_Invalid";
public static final String CODESYSTEM_CS_VS_EXP_MISMATCH = "CODESYSTEM_CS_VS_EXP_MISMATCH"; public static final String CODESYSTEM_CS_VS_EXP_MISMATCH = "CODESYSTEM_CS_VS_EXP_MISMATCH";
public static final String CODESYSTEM_CS_VS_WRONGSYSTEM = "CodeSystem_CS_VS_WrongSystem"; public static final String CODESYSTEM_CS_VS_WRONGSYSTEM = "CodeSystem_CS_VS_WrongSystem";
public static final String CODESYSTEM_CS_NO_SUPPLEMENT = "CODESYSTEM_CS_NO_SUPPLEMENT";
public static final String CODESYSTEM_CS_SUPP_CANT_CHECK = "CODESYSTEM_CS_SUPP_CANT_CHECK";
public static final String CODESYSTEM_CS_SUPP_INVALID_CODE = "CODESYSTEM_CS_SUPP_INVALID_CODE";
public static final String CODE_FOUND_IN_EXPANSION_HOWEVER_ = "Code_found_in_expansion_however_"; public static final String CODE_FOUND_IN_EXPANSION_HOWEVER_ = "Code_found_in_expansion_however_";
public static final String CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE = "Coding_has_no_system__cannot_validate"; public static final String CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE = "Coding_has_no_system__cannot_validate";
public static final String CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_ = "Contained_resource_does_not_appear_to_be_a_FHIR_resource_unknown_name_"; public static final String CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_ = "Contained_resource_does_not_appear_to_be_a_FHIR_resource_unknown_name_";

View File

@ -7,12 +7,12 @@ package org.hl7.fhir.utilities.validation;
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
@ -127,6 +127,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case ERROR: return "error"; case ERROR: return "error";
case WARNING: return "warning"; case WARNING: return "warning";
case INFORMATION: return "information"; case INFORMATION: return "information";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -136,6 +137,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case ERROR: return "http://hl7.org/fhir/issue-severity"; case ERROR: return "http://hl7.org/fhir/issue-severity";
case WARNING: return "http://hl7.org/fhir/issue-severity"; case WARNING: return "http://hl7.org/fhir/issue-severity";
case INFORMATION: return "http://hl7.org/fhir/issue-severity"; case INFORMATION: return "http://hl7.org/fhir/issue-severity";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -145,6 +147,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case ERROR: return "The issue is sufficiently important to cause the action to fail."; case ERROR: return "The issue is sufficiently important to cause the action to fail.";
case WARNING: return "The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired."; case WARNING: return "The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.";
case INFORMATION: return "The issue has no relation to the degree of success of the action."; case INFORMATION: return "The issue has no relation to the degree of success of the action.";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -154,6 +157,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case ERROR: return "Error"; case ERROR: return "Error";
case WARNING: return "Warning"; case WARNING: return "Warning";
case INFORMATION: return "Information"; case INFORMATION: return "Information";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -380,6 +384,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case TIMEOUT: return "timeout"; case TIMEOUT: return "timeout";
case THROTTLED: return "throttled"; case THROTTLED: return "throttled";
case INFORMATIONAL: return "informational"; case INFORMATIONAL: return "informational";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -414,6 +419,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case TIMEOUT: return "http://hl7.org/fhir/issue-type"; case TIMEOUT: return "http://hl7.org/fhir/issue-type";
case THROTTLED: return "http://hl7.org/fhir/issue-type"; case THROTTLED: return "http://hl7.org/fhir/issue-type";
case INFORMATIONAL: return "http://hl7.org/fhir/issue-type"; case INFORMATIONAL: return "http://hl7.org/fhir/issue-type";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -448,6 +454,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case TIMEOUT: return "An internal timeout has occurred."; case TIMEOUT: return "An internal timeout has occurred.";
case THROTTLED: return "The system is not prepared to handle this request due to load management."; case THROTTLED: return "The system is not prepared to handle this request due to load management.";
case INFORMATIONAL: return "A message unrelated to the processing success of the completed operation (examples of the latter include things like reminders of password expiry, system maintenance times, etc.)."; case INFORMATIONAL: return "A message unrelated to the processing success of the completed operation (examples of the latter include things like reminders of password expiry, system maintenance times, etc.).";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }
@ -482,6 +489,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
case TIMEOUT: return "Timeout"; case TIMEOUT: return "Timeout";
case THROTTLED: return "Throttled"; case THROTTLED: return "Throttled";
case INFORMATIONAL: return "Informational Note"; case INFORMATIONAL: return "Informational Note";
case NULL: return null;
default: return "?"; default: return "?";
} }
} }

View File

@ -106,8 +106,8 @@ Questionnaire_Q_EnableWhen_Self = Target for this question enableWhen can''t ref
Reference_REF_Aggregation = Reference is {0} which isn''t supported by the specified aggregation mode(s) for the reference ({1}) Reference_REF_Aggregation = Reference is {0} which isn''t supported by the specified aggregation mode(s) for the reference ({1})
Reference_REF_BadTargetType = Invalid Resource target type. Found {0}, but expected one of ({1}) Reference_REF_BadTargetType = Invalid Resource target type. Found {0}, but expected one of ({1})
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_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 matching profile for {0} among choices: {1} Reference_REF_CantMatchChoice = Unable to find a match for profile {0} among choices: {1}
Reference_REF_CantMatchType = Unable to find matching profile for {0} (by type) 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 ''{0}''
Reference_REF_CantResolveProfile = Unable to resolve the profile 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_Format1 = Relative URLs must be of the format [ResourceName]/[id], or a search URL is allowed ([type]?parameters. Encountered {0})
@ -217,7 +217,7 @@ Validation_VAL_Profile_NoCheckMax = {2}: Unable to check max allowed ({1}) due t
Validation_VAL_Profile_NoCheckMin = {2}: Unable to check minimum required ({1}) due to lack of slicing validation (from {0}) Validation_VAL_Profile_NoCheckMin = {2}: Unable to check minimum required ({1}) due to lack of slicing validation (from {0})
Validation_VAL_Profile_MultipleMatches = Found multiple matching profiles among choices: {0} Validation_VAL_Profile_MultipleMatches = Found multiple matching profiles among choices: {0}
Validation_VAL_Profile_NoDefinition = No definition found for resource type ''{0}'' Validation_VAL_Profile_NoDefinition = No definition found for resource type ''{0}''
Validation_VAL_Profile_NoMatch = Unable to find matching profile among choices: {0} Validation_VAL_Profile_NoMatch = Unable to find a match for the specified profile among choices: {0}
Validation_VAL_Profile_NoSnapshot = StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided Validation_VAL_Profile_NoSnapshot = StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided
Validation_VAL_Profile_NoType = The type of element {0} is not known, which is illegal. Valid types at this point are {1} Validation_VAL_Profile_NoType = The type of element {0} is not known, which is illegal. Valid types at this point are {1}
Validation_VAL_Profile_NotAllowed = This element is not allowed by the profile {0} Validation_VAL_Profile_NotAllowed = This element is not allowed by the profile {0}
@ -636,3 +636,6 @@ DISCRIMINATOR_BAD_PATH = Error processing path expression for disciminator: {0}
SLICING_CANNOT_BE_EVALUATED = Slicing cannot be evaluated: {0} SLICING_CANNOT_BE_EVALUATED = Slicing cannot be evaluated: {0}
TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE = Canonical URL ''{0}'' does not resolve TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE = Canonical URL ''{0}'' does not resolve
TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE = Canonical URL ''{0}'' refers to a resource that has the wrong type. Found {1} expecting one of {2} TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE = Canonical URL ''{0}'' refers to a resource that has the wrong type. Found {1} expecting one of {2}
CODESYSTEM_CS_NO_SUPPLEMENT = CodeSystem {0} is a supplement, so can't be used as a value in Coding.system
CODESYSTEM_CS_SUPP_CANT_CHECK = CodeSystem {0} cannot be found, so can't check if concepts are valid
CODESYSTEM_CS_SUPP_INVALID_CODE = The code ''{1}'' is not declared in the base CodeSystem {0} so is not valid in the supplement

View File

@ -834,6 +834,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
context.setTlogging(false); context.setTlogging(false);
if (url == null) { if (url == null) {
context.setCanRunWithoutTerminology(true); context.setCanRunWithoutTerminology(true);
context.setNoTerminologyServer(true);
return "n/a: No Terminology Server"; return "n/a: No Terminology Server";
} else { } else {
try { try {

View File

@ -129,10 +129,10 @@ public class Params {
cliContext.getBundleValidationRules().add(new BundleValidationRule(r, p)); cliContext.getBundleValidationRules().add(new BundleValidationRule(r, p));
} else if (args[i].equals(QUESTIONNAIRE)) { } else if (args[i].equals(QUESTIONNAIRE)) {
if (i + 1 == args.length) if (i + 1 == args.length)
throw new Error("Specified -questionnaire without indicating questionnaire file"); throw new Error("Specified -questionnaire without indicating questionnaire mode");
else { else {
String q = args[++i]; String q = args[++i];
cliContext.setQuestionnaireMode(QuestionnaireMode.valueOf(q)); cliContext.setQuestionnaireMode(QuestionnaireMode.fromCode(q));
} }
} else if (args[i].equals(NATIVE)) { } else if (args[i].equals(NATIVE)) {
cliContext.setDoNative(true); cliContext.setDoNative(true);

View File

@ -27,6 +27,7 @@ public class ProfileLoader {
try { try {
URL url = new URL(src + "?nocache=" + System.currentTimeMillis()); URL url = new URL(src + "?nocache=" + System.currentTimeMillis());
URLConnection c = url.openConnection(); URLConnection c = url.openConnection();
return IOUtils.toByteArray(c.getInputStream()); return IOUtils.toByteArray(c.getInputStream());
} catch (Exception e) { } catch (Exception e) {
throw new FHIRException("Unable to find definitions at URL '" + src + "': " + e.getMessage(), e); throw new FHIRException("Unable to find definitions at URL '" + src + "': " + e.getMessage(), e);

View File

@ -660,7 +660,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
@Override @Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, List<StructureDefinition> profiles) throws FHIRException { public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, List<StructureDefinition> profiles) throws FHIRException {
JsonParser parser = new JsonParser(context); JsonParser parser = new JsonParser(context, new ProfileUtilities(context, null, null, fpe));
parser.setupValidation(ValidationPolicy.EVERYTHING, errors); parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
long t = System.nanoTime(); long t = System.nanoTime();
Element e = parser.parse(object); Element e = parser.parse(object);
@ -2158,6 +2158,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return true; return true;
} }
} }
if (tr.getTargetProfile().isEmpty()) {
return true;
}
} }
return false; return false;
} }
@ -2400,7 +2403,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack); checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack);
else if (!noExtensibleWarnings) else if (!noExtensibleWarnings && !isOkExtension(value, vs))
txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeReference(binding.getValueSet()), vs.getUrl(), getErrorMessage(vr.getMessage())); txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeReference(binding.getValueSet()), vs.getUrl(), getErrorMessage(vr.getMessage()));
} else if (binding.getStrength() == BindingStrength.PREFERRED) { } else if (binding.getStrength() == BindingStrength.PREFERRED) {
if (baseOnly) { if (baseOnly) {
@ -2413,6 +2416,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2); hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2);
} }
private boolean isOkExtension(String value, ValueSet vs) {
if ("http://hl7.org/fhir/ValueSet/defined-types".equals(vs.getUrl())) {
return value.startsWith("http://hl7.org/fhirpath/System.");
}
return false;
}
private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) { private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) {
checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern); checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern); checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern);
@ -3166,36 +3176,53 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// really, there should only be one level for this (contained resources cannot contain // really, there should only be one level for this (contained resources cannot contain
// contained resources), but we'll leave that to some other code to worry about // contained resources), but we'll leave that to some other code to worry about
boolean wasContained = false; boolean wasContained = false;
while (stack != null && stack.getElement() != null) { NodeStack nstack = stack;
if (stack.getElement().getProperty().isResource()) { while (nstack != null && nstack.getElement() != null) {
if (nstack.getElement().getProperty().isResource()) {
// ok, we'll try to find the contained reference // ok, we'll try to find the contained reference
if (ref.equals("#") && stack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) { if (ref.equals("#") && nstack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) {
ResolvedReference rr = new ResolvedReference(); ResolvedReference rr = new ResolvedReference();
rr.setResource(stack.getElement()); rr.setResource(nstack.getElement());
rr.setFocus(stack.getElement()); rr.setFocus(nstack.getElement());
rr.setExternal(false); rr.setExternal(false);
rr.setStack(stack.push(stack.getElement(), -1, stack.getElement().getProperty().getDefinition(), stack.getElement().getProperty().getDefinition())); rr.setStack(nstack.push(nstack.getElement(), -1, nstack.getElement().getProperty().getDefinition(), nstack.getElement().getProperty().getDefinition()));
rr.getStack().qualifyPath(".ofType("+stack.getElement().fhirType()+")"); rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
return rr; return rr;
} }
if (stack.getElement().getSpecial() == SpecialElement.CONTAINED) { if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) {
wasContained = true; wasContained = true;
} }
IndexedElement res = getContainedById(stack.getElement(), ref.substring(1)); IndexedElement res = getContainedById(nstack.getElement(), ref.substring(1));
if (res != null) { if (res != null) {
ResolvedReference rr = new ResolvedReference(); ResolvedReference rr = new ResolvedReference();
rr.setResource(stack.getElement()); rr.setResource(nstack.getElement());
rr.setFocus(res.getMatch()); rr.setFocus(res.getMatch());
rr.setExternal(false); rr.setExternal(false);
rr.setStack(stack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); rr.setStack(nstack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
rr.getStack().qualifyPath(".ofType("+stack.getElement().fhirType()+")"); rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
return rr; return rr;
} }
} }
if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || stack.getElement().getSpecial() == SpecialElement.PARAMETER) { if (nstack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || nstack.getElement().getSpecial() == SpecialElement.PARAMETER) {
return null; // we don't try to resolve contained references across this boundary return null; // we don't try to resolve contained references across this boundary
} }
stack = stack.getParent(); nstack = nstack.getParent();
}
// try again, and work up the element parent list
if (ref.equals("#")) {
Element e = stack.getElement();
while (e != null) {
if (e.getProperty().isResource() && (e.getSpecial() != SpecialElement.CONTAINED)) {
ResolvedReference rr = new ResolvedReference();
rr.setResource(e);
rr.setFocus(e);
rr.setExternal(false);
rr.setStack(stack.push(e, -1, e.getProperty().getDefinition(), e.getProperty().getDefinition()));
rr.getStack().qualifyPath(".ofType("+e.fhirType()+")");
return rr;
}
e = e.getParentForValidator();
}
} }
return null; return null;
} else { } else {
@ -4007,7 +4034,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (element.getType().equals("CapabilityStatement")) { } else if (element.getType().equals("CapabilityStatement")) {
validateCapabilityStatement(errors, element, stack); validateCapabilityStatement(errors, element, stack);
} else if (element.getType().equals("CodeSystem")) { } else if (element.getType().equals("CodeSystem")) {
new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack); new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack, new ValidationOptions(stack.getWorkingLang()));
} else if (element.getType().equals("SearchParameter")) { } else if (element.getType().equals("SearchParameter")) {
new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack); new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack);
} else if (element.getType().equals("StructureDefinition")) { } else if (element.getType().equals("StructureDefinition")) {
@ -4951,15 +4978,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (inv.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice") && if (inv.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice") &&
ToolingExtensions.readBooleanExtension(inv, "http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice")) { ToolingExtensions.readBooleanExtension(inv, "http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice")) {
if (bpWarnings == BestPracticeWarningLevel.Hint) if (bpWarnings == BestPracticeWarningLevel.Hint)
hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + inv.getHuman() + msg + " [" + n.toString() + "]"); hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + (Utilities.noString(msg) ? "failed" : msg));
else if (bpWarnings == BestPracticeWarningLevel.Warning) else if (bpWarnings == BestPracticeWarningLevel.Warning)
warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + inv.getHuman() + msg + " [" + n.toString() + "]"); warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + (Utilities.noString(msg) ? "failed" : msg));
else if (bpWarnings == BestPracticeWarningLevel.Error) else if (bpWarnings == BestPracticeWarningLevel.Error)
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + inv.getHuman() + msg + " [" + n.toString() + "]"); rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + (Utilities.noString(msg) ? "failed" : msg));
} else if (inv.getSeverity() == ConstraintSeverity.ERROR) { } else if (inv.getSeverity() == ConstraintSeverity.ERROR) {
rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + inv.getHuman() + msg + " [" + n.toString() + "]"); rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + (Utilities.noString(msg) ? "failed" : msg));
} else if (inv.getSeverity() == ConstraintSeverity.WARNING) { } else if (inv.getSeverity() == ConstraintSeverity.WARNING) {
warning(errors, IssueType.INVARIANT, element.line(), element.line(), path, ok, inv.getKey() + ": " + inv.getHuman() + msg + " [" + n.toString() + "]"); warning(errors, IssueType.INVARIANT, element.line(), element.line(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + (Utilities.noString(msg) ? "failed" : msg));
} }
} }
} }

View File

@ -5,6 +5,7 @@ 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.CodeSystem;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
@ -12,10 +13,13 @@ import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 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.utilities.validation.ValidationOptions;
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.utils.NodeStack; import org.hl7.fhir.validation.instance.utils.NodeStack;
import ca.uhn.fhir.validation.ValidationResult;
public class CodeSystemValidator extends BaseValidator { public class CodeSystemValidator extends BaseValidator {
public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager) { public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager) {
@ -24,7 +28,7 @@ public class CodeSystemValidator extends BaseValidator {
this.timeTracker = timeTracker; this.timeTracker = timeTracker;
} }
public void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) { public void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack, ValidationOptions options) {
String url = cs.getNamedChildValue("url"); String url = cs.getNamedChildValue("url");
String content = cs.getNamedChildValue("content"); String content = cs.getNamedChildValue("content");
@ -52,6 +56,31 @@ public class CodeSystemValidator extends BaseValidator {
} }
} }
} // todo... try getting the value set the other way... } // todo... try getting the value set the other way...
String supp = cs.getNamedChildValue("supplements");
if (supp != null) {
if (context.supportsSystem(supp)) {
List<Element> concepts = cs.getChildrenByName("concept");
int ce = 0;
for (Element concept : concepts) {
validateSupplementConcept(errors, concept, stack.push(concept, ce, null, null), supp, options);
ce++;
}
} else {
if (cs.hasChildren("concept")) {
warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp);
}
}
}
}
private void validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) {
String code = concept.getChildValue("code");
if (!Utilities.noString(code)) {
org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, supp, code, null);
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code);
}
} }
private int countConcepts(Element cs) { private int countConcepts(Element cs) {

View File

@ -86,7 +86,9 @@ public class ValidatorHostContext {
} }
public void sliceNotes(String url, List<ValidationMessage> record) { public void sliceNotes(String url, List<ValidationMessage> record) {
if (sliceRecords != null) {
sliceRecords.put(url, record); sliceRecords.put(url, record);
}
} }
public ValidatorHostContext forContained(Element element) { public ValidatorHostContext forContained(Element element) {