Validate that binding references are present, and are for value sets

This commit is contained in:
Grahame Grieve 2020-12-09 10:06:20 +11:00
parent 2c4b4c4ca2
commit 1532a038c9
3 changed files with 64 additions and 1 deletions

View File

@ -352,6 +352,11 @@ public class I18nConstants {
public static final String SD_ED_TYPE_PROFILE_WRONG = "SD_ED_TYPE_PROFILE_WRONG";
public static final String SD_ED_TYPE_PROFILE_WRONG_TARGET = "SD_ED_TYPE_PROFILE_WRONG_TARGET";
public static final String SD_ED_TYPE_NO_TARGET_PROFILE = "SD_ED_TYPE_NO_TARGET_PROFILE";
public static final String SD_ED_SHOULD_BIND = "SD_ED_SHOULD_BIND";
public static final String SD_ED_SHOULD_BIND_WITH_VS = "SD_ED_SHOULD_BIND_WITH_VS";
public static final String SD_ED_BIND_UNKNOWN_VS = "SD_ED_BIND_UNKNOWN_VS";
public static final String SD_ED_BIND_NOT_VS = "SD_ED_BIND_NOT_VS";
public static final String SD_ED_BIND_NO_BINDABLE = "SD_ED_BIND_NO_BINDABLE";
public static final String SEARCHPARAMETER_BASE_WRONG = "SEARCHPARAMETER_BASE_WRONG";
public static final String SEARCHPARAMETER_EXP_WRONG = "SEARCHPARAMETER_EXP_WRONG";
public static final String SEARCHPARAMETER_NOTFOUND = "SEARCHPARAMETER_NOTFOUND";

View File

@ -628,3 +628,8 @@ XHTML_URL_DATA_NO_DATA = No data found in data: URL
XHTML_URL_DATA_DATA_INVALID_COMMA = Comma found in data portion of data URL: {0}
XHTML_URL_DATA_DATA_INVALID = The data should be valid base64 content for a data: URL: {0}
XHTML_URL_DATA_MIMETYPE = The mimetype potion of the data: URL is not valid ({1}) in URL: {0}
SD_ED_SHOULD_BIND = The element {0} has a type that should have a binding ({1}), but no binding is present
SD_ED_SHOULD_BIND_WITH_VS = The element {0} has a type that should have a binding ({1}), but the binding has no value set
SD_ED_BIND_UNKNOWN_VS = The valueSet reference {1} on element {0} could not be resolved
SD_ED_BIND_NOT_VS = The valueSet reference {1} on element {0} points to something that is not a value set ({2})
SD_ED_BIND_NO_BINDABLE = The element {0} has a binding, but no bindable types are present {1}

View File

@ -4,7 +4,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.convertors.VersionConvertor_10_50;
import org.hl7.fhir.convertors.VersionConvertor_14_50;
@ -19,12 +21,15 @@ 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.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
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;
@ -115,10 +120,12 @@ public class StructureDefinitionValidator extends BaseValidator {
private void validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd) {
boolean typeMustSupport = false;
List<Element> types = element.getChildrenByName("type");
Set<String> typeCodes = new HashSet<>();
for (Element type : types) {
if (hasMustSupportExtension(type)) {
typeMustSupport = true;
}
typeCodes.add(type.getChildValue("code"));
// check the stated profile - must be a constraint on the type
if (snapshot || sd != null) {
validateElementType(errors, type, stack.push(type, -1, null, null), sd, element.getChildValue("path"));
@ -131,6 +138,50 @@ public class StructureDefinitionValidator extends BaseValidator {
hint(errors, IssueType.EXCEPTION, stack.getLiteralPath(), hasSnapshot || "true".equals(element.getChildValue("mustSupport")), I18nConstants.SD_NESTED_MUST_SUPPORT_DIFF, element.getNamedChildValue("path"));
}
}
if (element.hasChild("binding")) {
Element binding = element.getNamedChild("binding");
validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, element.getNamedChildValue("path"));
} else {
// this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back
// String bt = boundType(typeCodes);
// hint(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path"), bt);
}
}
private String boundType(Set<String> typeCodes) {
for (String tc : typeCodes) {
if (Utilities.existsInList(tc, "code", "Coding", "CodeableConcept", "Quantity", "CodeableReference")) {
return tc;
}
}
return null;
}
private String bindableType(Set<String> typeCodes) {
String ret = boundType(typeCodes);
if (ret != null) {
return ret;
}
for (String tc : typeCodes) {
if (Utilities.existsInList(tc, "string", "uri", "CodeableConcept", "Quantity", "CodeableReference")) {
return tc;
}
}
return null;
}
private void validateBinding(List<ValidationMessage> errors, Element binding, NodeStack stack, Set<String> typeCodes, boolean snapshot, String path) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bindableType(typeCodes) != null, I18nConstants.SD_ED_BIND_NO_BINDABLE, path, typeCodes.toString());
if (binding.hasChild("valueSet")) {
Element valueSet = binding.getNamedChild("valueSet");
String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("refernece");
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) {
Resource vs = context.fetchResource(Resource.class, ref);
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null, I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType());
}
}
}
}
private void validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) {
@ -231,9 +282,11 @@ public class StructureDefinitionValidator extends BaseValidator {
String t = determineBaseType(sd);
if (t == null) {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
} else if (!VersionUtilities.isR5Ver(context.getVersion())) {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t) || "Resource".equals(t), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource");
} else {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(t), I18nConstants.SD_ED_TYPE_PROFILE_WRONG_TARGET, p, t, code, path, "Canonical Resource");
}
}
}
} else {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.SD_ED_TYPE_NO_TARGET_PROFILE, code);