Skip to content

Commit 6f49864

Browse files
authored
feat: Add full support for ValueSet filter operators in InMemoryTerminologyServerValidationSupport (#6904)
* feat: add full support for ValueSet filters in in-memory terminology validation This adds support for all FHIR-defined ValueSet filter operators (e.g. is-a, descendent-of, is-not-a, in, regex, exists, etc.) to the in-memory terminology validator (InMemoryTerminologyServerValidationSupport). Supports: - Structural filters: is-a, descendent-of, is-not-a, generalizes, child-of, descendent-leaf - Equality/regex/inclusion: equal (=), regex, in, not-in - Existence: exists Adds internal indexing for concept code/display fields to enable efficient traversal and matching. Follows case-sensitivity rules defined by CodeSystem. Also includes a full unit test suite for all filters, covering both code and display properties where relevant. Fixes issues where descendant-based filters were not honored during expansion in tests relying on in-memory terminology (e.g. no JPA preload). Fixes #4447, #6255 * fix(validation): error on filtered ValueSet includes against ignored CodeSystem When a ValueSet include specifies filters (e.g. “is-a”) but the referenced CodeSystem is configured as NOT_PRESENT/ignored, in-memory expansion cannot apply structural filters. Add a guard to throw an ExpansionCouldNotBeCompletedInternallyException in this scenario rather than silently returning an empty result. * test(validation): expect in-memory validation to fail for delta-based hierarchy Previously we asserted success on an in-memory hierarchy check, but the expander cannot apply ISA filters against a NOT_PRESENT CodeSystem patched only via `$apply-codesystem-delta-add`. Update the test to assert that `validateCode(...)` returns a non-OK outcome containing “cannot apply filters” for the in-memory pass, then continue to validate via the deferred/pre-expanded path.
1 parent 8ff4604 commit 6f49864

File tree

5 files changed

+1094
-58
lines changed

5 files changed

+1094
-58
lines changed

hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,10 @@ public void testValidateCodeInValueSet_HierarchicalAndEnumeratedValueset() {
141141
ValidationSupportContext ctx = new ValidationSupportContext(myValidationSupport);
142142
ConceptValidationOptions options = new ConceptValidationOptions();
143143

144-
// In memory - Hierarchy in existing CS
145-
146-
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "child10", null, "http://vs");
147-
assertNotNull(outcome);
148-
assertTrue(outcome.isOk());
149-
assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails());
150-
151144
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "childX", null, "http://vs");
152145
assertNotNull(outcome);
153146
assertFalse(outcome.isOk());
154-
assertThat(outcome.getMessage()).contains("Unknown code 'http://cs#childX' for in-memory expansion of ValueSet 'http://vs'");
147+
assertThat(outcome.getMessage()).contains("cannot apply filters");
155148

156149
// In memory - Enumerated in non-present CS
157150

@@ -248,13 +241,13 @@ public void testValidateCodeInValueSet_HierarchicalAndEnumeratedValueset_Hiberna
248241

249242
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "child10", null, "http://vs");
250243
assertNotNull(outcome);
251-
assertTrue(outcome.isOk());
252-
assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getSourceDetails());
244+
assertFalse(outcome.isOk());
245+
assertThat(outcome.getMessage()).contains("cannot apply filters");
253246

254247
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "childX", null, "http://vs");
255248
assertNotNull(outcome);
256249
assertFalse(outcome.isOk());
257-
assertThat(outcome.getMessage()).contains("Unknown code 'http://cs#childX' for in-memory expansion of ValueSet 'http://vs'");
250+
assertThat(outcome.getMessage()).contains("cannot apply filters");
258251

259252
// In memory - Enumerated in non-present CS
260253

hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,26 @@ private boolean expandValueSetR5IncludeOrExclude(
10211021
boolean isIncludeCodeSystemIgnored = includeOrExcludeSystemResource != null
10221022
&& includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT;
10231023

1024+
boolean isIncludeFromSystem = isNotBlank(theInclude.getSystem())
1025+
&& theInclude.getValueSet().isEmpty();
1026+
boolean isIncludeWithFilter = !theInclude.getFilter().isEmpty();
1027+
1028+
// if we can’t load the CS and we’re configured to ignore it...
1029+
if (isIncludeCodeSystemIgnored && !theInclude.getFilter().isEmpty()) {
1030+
// We can’t apply any structural (ISA/DESCENDENT-OF/etc..) filters if the CS is absent → fail
1031+
String msg = "Unable to expand ValueSet: cannot apply filters '" + theInclude.getFilter()
1032+
+ "' because CodeSystem '" + theInclude.getSystem()
1033+
+ "' is ignored/not-present";
1034+
1035+
throw new ExpansionCouldNotBeCompletedInternallyException(
1036+
Msg.code(2646) + msg,
1037+
new CodeValidationIssue(
1038+
msg,
1039+
IssueSeverity.ERROR,
1040+
CodeValidationIssueCode.NOT_FOUND,
1041+
CodeValidationIssueCoding.NOT_FOUND));
1042+
}
1043+
10241044
if (includeOrExcludeSystemResource == null || isIncludeCodeSystemIgnored) {
10251045

10261046
if (theWantCode != null) {
@@ -1097,9 +1117,6 @@ private boolean expandValueSetR5IncludeOrExclude(
10971117
}
10981118
}
10991119
} else {
1100-
boolean isIncludeFromSystem = isNotBlank(theInclude.getSystem())
1101-
&& theInclude.getValueSet().isEmpty();
1102-
boolean isIncludeWithFilter = !theInclude.getFilter().isEmpty();
11031120
if (isIncludeFromSystem && !isIncludeWithFilter) {
11041121
if (isIncludeWithDeclaredConcepts) {
11051122
theInclude.getConcept().stream()
@@ -1180,22 +1197,24 @@ private boolean expandValueSetR5IncludeOrExclude(
11801197
}
11811198

11821199
boolean retVal = false;
1200+
ValueSetExpansionFilterContext valueSetExpansionFilterContext =
1201+
new ValueSetExpansionFilterContext(includeOrExcludeSystemResource, theInclude.getFilter());
11831202

11841203
for (FhirVersionIndependentConcept next : nextCodeList) {
11851204
if (includeOrExcludeSystemResource != null && theWantCode != null) {
1186-
boolean matches;
1187-
if (includeOrExcludeSystemResource.getCaseSensitive()) {
1188-
matches = theWantCode.equals(next.getCode());
1189-
} else {
1190-
matches = theWantCode.equalsIgnoreCase(next.getCode());
1191-
}
1205+
boolean matches = includeOrExcludeSystemResource.getCaseSensitive()
1206+
? theWantCode.equals(next.getCode())
1207+
: theWantCode.equalsIgnoreCase(next.getCode());
1208+
11921209
if (!matches) {
11931210
continue;
11941211
}
11951212
}
11961213

1197-
theConsumer.accept(next);
1198-
retVal = true;
1214+
if (!valueSetExpansionFilterContext.isFiltered(next)) {
1215+
theConsumer.accept(next);
1216+
retVal = true;
1217+
}
11991218
}
12001219

12011220
return retVal;

0 commit comments

Comments
 (0)