Add more validation around ValueSet.compose.include.system - must be a proper URL, and not a reference to a supplement

This commit is contained in:
Grahame Grieve 2024-03-06 13:29:35 +11:00
parent 51f5309cc7
commit 0f08bb4e49
4 changed files with 87 additions and 31 deletions

View File

@ -1060,6 +1060,15 @@ public class I18nConstants {
public static final String UNABLE_TO_INFER_CODESYSTEM = "UNABLE_TO_INFER_CODESYSTEM";
public static final String CODE_CASE_DIFFERENCE = "CODE_CASE_DIFFERENCE";
public static final String ILLEGAL_PROPERTY = "ILLEGAL_PROPERTY";
public static final String VALUESET_INCLUDE_SYSTEM_ABSOLUTE = "VALUESET_INCLUDE_SYSTEM_ABSOLUTE";
public static final String VALUESET_INCLUDE_SYSTEM_ABSOLUTE_FRAG = "VALUESET_INCLUDE_SYSTEM_ABSOLUTE_FRAG";
public static final String CODESYSTEM_CS_NO_VS_SUPPLEMENT1 = "CODESYSTEM_CS_NO_VS_SUPPLEMENT1";
public static final String CODESYSTEM_CS_NO_VS_SUPPLEMENT2 = "CODESYSTEM_CS_NO_VS_SUPPLEMENT2";
public static final String CODESYSTEM_CS_SUPP_NO_SUPP = "CODESYSTEM_CS_SUPP_NO_SUPP";
public static final String VALUESET_INCLUDE_CS_CONTENT = "VALUESET_INCLUDE_CS_CONTENT";
public static final String VALUESET_INCLUDE_CSVER_CONTENT = "VALUESET_INCLUDE_CSVER_CONTENT";
public static final String VALUESET_INCLUDE_CS_SUPPLEMENT = "VALUESET_INCLUDE_CS_SUPPLEMENT";
public static final String VALUESET_INCLUDE_CSVER_SUPPLEMENT = "VALUESET_INCLUDE_CSVER_SUPPLEMENT";
}

View File

@ -551,6 +551,8 @@ XHTML_URL_INVALID_CHARS_one = URL contains Invalid Character ({1})
XHTML_URL_INVALID_CHARS_other = URL contains {0} Invalid Characters ({1})
TERMINOLOGY_TX_SYSTEM_HTTPS = The system URL ''{0}'' wrongly starts with https: not http:
CODESYSTEM_CS_NO_VS_NOTCOMPLETE = Review the All Codes Value Set - incomplete CodeSystems generally should not have an all codes value set specified
CODESYSTEM_CS_NO_VS_SUPPLEMENT1 = CodeSystems supplements should not have an all codes value set specified, and if they do, it must match the base code system
CODESYSTEM_CS_NO_VS_SUPPLEMENT2 = CodeSystems supplements should not have an all codes value set specified, and if they do, it must match the base code system, and this one does not (''{0}'')
TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE = if identifier.system is ''urn:ietf:rfc:3986'', then the identifier.value must be a full URI (e.g. start with a scheme), not ''{0}''
TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID = Stated Attachment Size {0} is not valid
TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT = Stated Attachment Size {0} does not match actual attachment size {1}
@ -1056,7 +1058,6 @@ VALUESET_CIRCULAR_REFERENCE = Found a circularity pointing to {0} processing Val
VALUESET_SUPPLEMENT_MISSING_one = Required supplement not found: {1}
VALUESET_SUPPLEMENT_MISSING_other = Required supplements not found: {1}
CONCEPTMAP_VS_TOO_MANY_CODES = The concept map has too many codes to validate ({0})
CONCEPTMAP_VS_CONCEPT_CODE_UNKNOWN_SYSTEM = The code ''{1}'' comes from the system {0} which could not be found, so it''s not known whether it''s valid in the value set ''{2}''
CONCEPTMAP_VS_INVALID_CONCEPT_CODE = The code ''{1}'' in the system {0} is not valid in the value set ''{2}''
CONCEPTMAP_VS_INVALID_CONCEPT_CODE_VER = The code ''{2}'' in the system {0} version {1} is not valid in the value set ''{3}''
VALUESET_INC_TOO_MANY_CODES = The value set include has too many codes to validate ({0}), so each individual code has not been checked
@ -1118,3 +1119,11 @@ VALUESET_INCLUDE_CSVER_MULTI_FOUND = Multiple matching contained code systems fo
CODE_CASE_DIFFERENCE = The code ''{0}'' differs from the correct code ''{1}'' by case. Although the code system ''{2}'' is case insensitive, implementers are strongly encouraged to use the correct case anyway
SCT_NO_MRCM = Not validated against the Machine Readable Concept Model (MRCM)
ILLEGAL_PROPERTY = The property ''{0}'' is invalid
VALUESET_INCLUDE_SYSTEM_ABSOLUTE = URI values in ValueSet.compose.include.system must be absolute
VALUESET_INCLUDE_SYSTEM_ABSOLUTE_FRAG = URI values in ValueSet.compose.include.system must be absolute. To reference a contained code system, use the full CodeSystem URL and reference it using the http://hl7.org/fhir/StructureDefinition/valueset-system extension
CODESYSTEM_CS_SUPP_NO_SUPP = The code system is marked as a supplement, but it does not define what code system it supplements
VALUESET_INCLUDE_CS_CONTENT = The value set references CodeSystem ''{0}'' which has status ''{1}''
VALUESET_INCLUDE_CSVER_CONTENT = The value set references CodeSystem ''{0}'' version ''{2}'' which has status ''{1}''
VALUESET_INCLUDE_CS_SUPPLEMENT = The value set references CodeSystem ''{0}'' which is a supplement. It must reference the underlying CodeSystem ''{1}'' and use the http://hl7.org/fhir/StructureDefinition/valueset-supplement extension for the supplement
VALUESET_INCLUDE_CSVER_SUPPLEMENT = The value set references CodeSystem ''{0}'' version ''{2}'' which is a supplement. It must reference the underlying CodeSystem ''{1}'' and use the http://hl7.org/fhir/StructureDefinition/valueset-supplement extension for the supplement

View File

@ -33,7 +33,20 @@ public class CodeSystemValidator extends BaseValidator {
String vsu = cs.getNamedChildValue("valueSet", false);
if (!Utilities.noString(vsu)) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE);
if ("supplement".equals(content)) {
CodeSystem csB = context.fetchCodeSystem(supp);
if (csB != null) {
if (csB.hasValueSet()) {
warning(errors, "2024-03-06", IssueType.BUSINESSRULE, stack.getLiteralPath(), vsu.equals(vsu), I18nConstants.CODESYSTEM_CS_NO_VS_SUPPLEMENT2, csB.getValueSet());
} else {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NO_VS_SUPPLEMENT1);
}
} else {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE);
}
} else {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE);
}
ValueSet vs;
try {
vs = context.fetchResourceWithException(ValueSet.class, vsu);
@ -61,22 +74,26 @@ public class CodeSystemValidator extends BaseValidator {
}
} // todo... try getting the value set the other way...
if (supp != null) {
if (context.supportsSystem(supp, options.getFhirVersion())) {
List<Element> concepts = cs.getChildrenByName("concept");
int ce = 0;
for (Element concept : concepts) {
NodeStack nstack = stack.push(concept, ce, null, null);
if (ce == 0) {
rule(errors, "2023-08-15", IssueType.INVALID, nstack, !"not-present".equals(content), I18nConstants.CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED);
if ("supplement".equals(content) || supp != null) {
if (rule(errors, "2024-03-06", IssueType.BUSINESSRULE, stack.getLiteralPath(), !Utilities.noString(supp), I18nConstants.CODESYSTEM_CS_SUPP_NO_SUPP)) {
if (context.supportsSystem(supp, options.getFhirVersion())) {
List<Element> concepts = cs.getChildrenByName("concept");
int ce = 0;
for (Element concept : concepts) {
NodeStack nstack = stack.push(concept, ce, null, null);
if (ce == 0) {
rule(errors, "2023-08-15", IssueType.INVALID, nstack, !"not-present".equals(content), I18nConstants.CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED);
}
ok = validateSupplementConcept(errors, concept, nstack, supp, options) && ok;
ce++;
}
} else {
if (cs.hasChildren("concept")) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp);
}
ok = validateSupplementConcept(errors, concept, nstack, supp, options) && ok;
ce++;
}
} else {
if (cs.hasChildren("concept")) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp);
}
} else {
ok = false;
}
}

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.validation.instance.type;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Resource;
@ -141,23 +142,26 @@ public class ValueSetValidator extends BaseValidator {
if (valuesets.size() > 1) {
warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, stack.getLiteralPath(), false, I18nConstants.VALUESET_IMPORT_UNION_INTERSECTION);
}
if (system != null && system.startsWith("#")) {
List<Element> cs = new ArrayList<>();
for (Element contained : vsSrc.getChildrenByName("contained")) {
if (("#"+contained.getIdBase()).equals(system)) {
if (rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), "CodeSystem".equals(contained.fhirType()), I18nConstants.VALUESET_INCLUDE_CS_NOT_CS, system, contained.fhirType())) {
if (version == null || version.equals(contained.getChildValue("version"))) {
cs.add(contained);
}
} else {
ok = false;
if (system != null) {
rule(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), Utilities.isAbsoluteUrl(system), system.startsWith("#") ? I18nConstants.VALUESET_INCLUDE_SYSTEM_ABSOLUTE_FRAG : I18nConstants.VALUESET_INCLUDE_SYSTEM_ABSOLUTE, system);
if (system.startsWith("#")) {
List<Element> cs = new ArrayList<>();
for (Element contained : vsSrc.getChildrenByName("contained")) {
if (("#"+contained.getIdBase()).equals(system)) {
ok = false; // see absolute check above.
if (rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), "CodeSystem".equals(contained.fhirType()), I18nConstants.VALUESET_INCLUDE_CS_NOT_CS, system, contained.fhirType())) {
if (version == null || version.equals(contained.getChildValue("version"))) {
cs.add(contained);
}
}
}
}
}
if (cs.isEmpty()) {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_NOT_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_NOT_FOUND, system, version) && ok;
} else {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), cs.size() == 1, version == null ? I18nConstants.VALUESET_INCLUDE_CS_MULTI_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_MULTI_FOUND, system, version) && ok;
if (cs.isEmpty()) {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_NOT_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_NOT_FOUND, system, version) && ok;
} else {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), cs.size() == 1, version == null ? I18nConstants.VALUESET_INCLUDE_CS_MULTI_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_MULTI_FOUND, system, version) && ok;
}
}
}
List<Element> concepts = include.getChildrenByName("concept");
@ -165,6 +169,23 @@ public class ValueSetValidator extends BaseValidator {
CodeSystemChecker slv = getSystemValidator(system, errors);
if (!Utilities.noString(system)) {
CodeSystem cs = context.fetchCodeSystem(system, version);
if (cs != null) { // if it's null, we can't analyse this
switch (cs.getContent()) {
case EXAMPLE:
warning(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_CONTENT : I18nConstants.VALUESET_INCLUDE_CSVER_CONTENT, system, cs.getContent().toCode(), version);
break;
case FRAGMENT:
hint(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_CONTENT : I18nConstants.VALUESET_INCLUDE_CSVER_CONTENT, system, cs.getContent().toCode(), version);
break;
case SUPPLEMENT:
ok = rule(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_SUPPLEMENT : I18nConstants.VALUESET_INCLUDE_CSVER_SUPPLEMENT, system, cs.getSupplements(), version) && ok;
break;
default:
break;
}
}
boolean systemOk = true;
int cc = 0;
List<VSCodingValidationRequest> batch = new ArrayList<>();