mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-03-09 14:33:32 +00:00
Mb implement token :not inmemory (#3784)
Implement token :not and fix :not-in in InMemoryMatcher Both `:not` and `:not-in` require that NONE of the values in an OR-list match, so we need some machinery to do a none-match instead of any-match.
This commit is contained in:
parent
6ba84e1c51
commit
1785c07283
@ -95,4 +95,13 @@ public enum TokenParamModifier {
|
||||
return VALUE_TO_ENUM.get(theValue);
|
||||
}
|
||||
|
||||
public boolean isNegative() {
|
||||
switch (this) {
|
||||
case NOT:
|
||||
case NOT_IN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TokenParamModifierTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource()
|
||||
void negativeModifiers(TokenParamModifier theTokenParamModifier) {
|
||||
EnumSet<TokenParamModifier> negativeSet = EnumSet.of(
|
||||
TokenParamModifier.NOT,
|
||||
TokenParamModifier.NOT_IN
|
||||
);
|
||||
|
||||
assertEquals(negativeSet.contains(theTokenParamModifier), theTokenParamModifier.isNegative());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
type: add
|
||||
issue: 3784
|
||||
title: "The InMemoryMatcher used by Subscription matching not supports the token `:not` modifier.
|
||||
The `:not-in` modifier was also corrected for cases of multiple values in a `,` separated or-list."
|
@ -42,6 +42,7 @@ import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.util.MetaUtil;
|
||||
@ -308,8 +309,27 @@ public class InMemoryResourceMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchParams(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) {
|
||||
return theNextAnd.stream().anyMatch(token -> matchParam(theModelConfig, theResourceName, theParamName, paramDef, theSearchParams, token));
|
||||
private boolean matchParams(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theOrList, ResourceIndexedSearchParams theSearchParams) {
|
||||
|
||||
boolean isNegativeTest = isNegative(theParamDef, theOrList);
|
||||
// negative tests like :not and :not-in must not match any or-clause, so we invert the quantifier.
|
||||
if (isNegativeTest) {
|
||||
return theOrList.stream().allMatch(token -> matchParam(theModelConfig, theResourceName, theParamName, theParamDef, theSearchParams, token));
|
||||
} else {
|
||||
return theOrList.stream().anyMatch(token -> matchParam(theModelConfig, theResourceName, theParamName, theParamDef, theSearchParams, token));
|
||||
}
|
||||
}
|
||||
|
||||
/** Some modifiers are negative, and must match NONE of their or-list */
|
||||
private boolean isNegative(RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theOrList) {
|
||||
if (theParamDef.getParamType().equals(RestSearchParameterTypeEnum.TOKEN)) {
|
||||
TokenParam tokenParam = (TokenParam) theOrList.get(0);
|
||||
TokenParamModifier modifier = tokenParam.getModifier();
|
||||
return modifier != null && modifier.isNegative();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, ResourceIndexedSearchParams theSearchParams, IQueryParameterType theToken) {
|
||||
@ -322,6 +342,7 @@ public class InMemoryResourceMatcher {
|
||||
|
||||
/**
|
||||
* Checks whether a query parameter of type token matches one of the search parameters of an in-memory resource.
|
||||
* The :not modifier is supported.
|
||||
* The :in and :not-in qualifiers are supported only if a bean implementing IValidationSupport is available.
|
||||
* Any other qualifier will be ignored and the match will be treated as unqualified.
|
||||
* @param theModelConfig a model configuration
|
||||
@ -343,6 +364,8 @@ public class InMemoryResourceMatcher {
|
||||
return theSearchParams.myTokenParams.stream()
|
||||
.filter(t -> t.getParamName().equals(theParamName))
|
||||
.noneMatch(t -> systemContainsCode(theQueryParam, t));
|
||||
case NOT:
|
||||
return !theSearchParams.matchParam(theModelConfig, theResourceName, theParamName, theParamDef, theQueryParam);
|
||||
default:
|
||||
return theSearchParams.matchParam(theModelConfig, theResourceName, theParamName, theParamDef, theQueryParam);
|
||||
}
|
||||
@ -426,6 +449,8 @@ public class InMemoryResourceMatcher {
|
||||
case NOT_IN:
|
||||
// Support for these qualifiers is dependent on an implementation of IValidationSupport being available to delegate the check to
|
||||
return getValidationSupportOrNull() != null;
|
||||
case NOT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -140,10 +140,15 @@ public class InMemoryResourceMatcherR5Test {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedNot() {
|
||||
InMemoryMatchResult result = myInMemoryResourceMatcher.match("code" + TokenParamModifier.NOT.getValue() + "=" + OBSERVATION_CODE, myObservation, mySearchParams);
|
||||
assertFalse(result.supported());
|
||||
assertEquals("Parameter: <code:not> Reason: Qualified parameter not supported", result.getUnsupportedReason());
|
||||
public void testSupportedNot() {
|
||||
String criteria = "code" + TokenParamModifier.NOT.getValue() + "=" + OBSERVATION_CODE + ",a_different_code";
|
||||
InMemoryMatchResult result = myInMemoryResourceMatcher.match(criteria, myObservation, mySearchParams);
|
||||
assertTrue(result.supported());
|
||||
assertFalse(result.matched(), ":not must not match any of the OR-list");
|
||||
|
||||
result = myInMemoryResourceMatcher.match("code:not=a_different_code,and_another", myObservation, mySearchParams);
|
||||
assertTrue(result.supported());
|
||||
assertTrue(result.matched(), ":not matches when NONE match");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -184,12 +189,20 @@ public class InMemoryResourceMatcherR5Test {
|
||||
|
||||
@Test
|
||||
public void testSupportedNotIn_NoMatch() {
|
||||
IValidationSupport.CodeValidationResult codeValidationResult = new IValidationSupport.CodeValidationResult().setCode(OBSERVATION_CODE);
|
||||
when(myValidationSupport.validateCode(any(), any(), any(), any(), any(), any())).thenReturn(codeValidationResult);
|
||||
IValidationSupport.CodeValidationResult matchResult = new IValidationSupport.CodeValidationResult().setCode(OBSERVATION_CODE);
|
||||
IValidationSupport.CodeValidationResult noMatchResult = new IValidationSupport.CodeValidationResult()
|
||||
.setSeverity(IValidationSupport.IssueSeverity.ERROR)
|
||||
.setMessage("not in");
|
||||
|
||||
InMemoryMatchResult result = myInMemoryResourceMatcher.match("code" + TokenParamModifier.NOT_IN.getValue() + "=" + OBSERVATION_CODE_VALUE_SET_URI, myObservation, mySearchParams);
|
||||
// mock 2 value sets. Once containing the code, and one not.
|
||||
String otherValueSet = OBSERVATION_CODE_VALUE_SET_URI + "-different";
|
||||
when(myValidationSupport.validateCode(any(), any(), any(), any(), any(), eq(OBSERVATION_CODE_VALUE_SET_URI))).thenReturn(matchResult);
|
||||
when(myValidationSupport.validateCode(any(), any(), any(), any(), any(), eq(otherValueSet))).thenReturn(noMatchResult);
|
||||
|
||||
String criteria = "code" + TokenParamModifier.NOT_IN.getValue() + "=" + OBSERVATION_CODE_VALUE_SET_URI + "," + otherValueSet;
|
||||
InMemoryMatchResult result = myInMemoryResourceMatcher.match(criteria, myObservation, mySearchParams);
|
||||
assertTrue(result.supported());
|
||||
assertFalse(result.matched());
|
||||
assertFalse(result.matched(), ":not-in matches when NONE of the OR-list match");
|
||||
|
||||
verify(myValidationSupport).validateCode(any(), any(), eq(OBSERVATION_CODE_SYSTEM), eq(OBSERVATION_CODE), isNull(), eq(OBSERVATION_CODE_VALUE_SET_URI));
|
||||
}
|
||||
|
@ -745,24 +745,6 @@ public class InMemorySubscriptionMatcherR4Test {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchTokenWithNotModifierUnsupported() {
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||
patient.addName().setFamily("Tester").addGiven("Joe");
|
||||
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
SearchParameterMap params;
|
||||
|
||||
params = new SearchParameterMap();
|
||||
params.add(Patient.SP_GENDER, new TokenParam(null, "male"));
|
||||
assertMatched(patient, params);
|
||||
|
||||
params = new SearchParameterMap();
|
||||
params.add(Patient.SP_GENDER, new TokenParam(null, "male").setModifier(TokenParamModifier.NOT));
|
||||
assertUnsupported(patient, params);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchTokenWrongParam() {
|
||||
Patient p1 = new Patient();
|
||||
|
Loading…
x
Reference in New Issue
Block a user