From ef5bbbf16b26b831bf750f560a311ea9a5bd92d0 Mon Sep 17 00:00:00 2001 From: Troy Biesterfeld Date: Tue, 20 Oct 2020 14:26:44 -0500 Subject: [PATCH] issue #1351 - Check search parameter combinations Signed-off-by: Troy Biesterfeld --- docs/src/pages/guides/FHIRServerUsersGuide.md | 18 +++ .../ibm/fhir/config/FHIRConfiguration.java | 1 + .../search/exception/SearchExceptionUtil.java | 30 +++- .../com/ibm/fhir/search/util/SearchUtil.java | 122 +++++++++++++- .../SearchParameterRestrictionTest.java | 153 +++++++++++++++--- .../config/tenant7/fhir-server-config.json | 25 ++- 6 files changed, 306 insertions(+), 43 deletions(-) diff --git a/docs/src/pages/guides/FHIRServerUsersGuide.md b/docs/src/pages/guides/FHIRServerUsersGuide.md index fa6c9c127dd..0688d337c6b 100644 --- a/docs/src/pages/guides/FHIRServerUsersGuide.md +++ b/docs/src/pages/guides/FHIRServerUsersGuide.md @@ -1520,9 +1520,15 @@ This section contains reference information about each of the configuration prop |`fhirServer/resources/Resource/interactions`|string list|A list of strings that represent the RESTful interactions (create, read, vread, update, patch, delete, history, and/or search) supported for resource types. Omitting this property is equivalent to supporting all FHIR interactions for the supported resources. An empty list, `[]`, can be used to indicate that no REST methods are supported. This property can be overridden for specific resource types via the `fhirServer/resources//interactions` property.| |`fhirServer/resources/Resource/searchParameters`|object|The set of search parameters to support for all supported resource types. Omitting this property is equivalent to supporting all search parameters in the server's registry that apply to resource type "Resource" (all resources). An empty object, `{}`, can be used to indicate that no global search parameters are supported.| |`fhirServer/resources/Resource/searchParameters/`|string|The URL of the search parameter definition to use for the search parameter ``. Individual resource types may override this value via `fhirServer/resources//searchParameters/`| +|`fhirServer/resources/Resource/searchIncludes`|string list|A comma-separated list of \_include values supported for all resource types. Individual resource types may override this value via `fhirServer/resources//searchIncludes`. Omitting this property is equivalent to supporting all \_include values for the supported resources. An empty list, `[]`, can be used to indicate that no \_include values are supported.| +|`fhirServer/resources/Resource/searchRevIncludes`|string list|A comma-separated list of \_revinclude values supported for all resource types. Individual resource types may override this value via `fhirServer/resources//searchRevIncludes`. Omitting this property is equivalent to supporting all \_revinclude values for the supported resources. An empty list, `[]`, can be used to indicate that no \_revinclude values are supported.| +|`fhirServer/resources/Resource/searchParameterCombinations`|string list|A comma-separated list of search parameter combinations supported for all resource types. Each search parameter combination is a string, where a plus sign, `+`, separates the search parameters that can be used in combination. To indicate that searching without any search parameters is allowed, an empty string must be included in the list. Including an asterisk, `*`, in the list indicates support of any search parameter combination. Individual resource types may override this value via `fhirServer/resources//searchParameterCombinations`. Omitting this property is equivalent to supporting any search parameter combination.| |`fhirServer/resources//interactions`|string list|A list of strings that represent the RESTful interactions (create, read, vread, update, patch, delete, history, and/or search) to support for this resource type. For resources without the property, the value of `fhirServer/resources/Resource/interactions` is used.| |`fhirServer/resources//searchParameters`|object|The set of search parameters to support for this resource type. Global search parameters defined on the `Resource` resource can be overridden on a per-resourceType basis.| |`fhirServer/resources//searchParameters/`|string|The URL of the search parameter definition to use for the search parameter `` on resources of type ``.| +|`fhirServer/resources//searchIncludes`|string list|A comma-separated list of \_include values supported for this resource type. An empty list, `[]`, can be used to indicate that no \_include values are supported. For resources without the property, the value of `fhirServer/resources/Resource/searchIncludes` is used.| +|`fhirServer/resources//searchRevIncludes`|string list|A comma-separated list of \_revinclude values supported for this resource type. An empty list, `[]`, can be used to indicate that no \_revinclude values are supported. For resources without the property, the value of `fhirServer/resources/Resource/searchRevIncludes` is used.| +|`fhirServer/resources//searchParameterCombinations`|string list|A comma-separated list of search parameter combinations supported for this resource type. Each search parameter combination is a string, where a plus sign, `+`, separates the search parameters that can be used in combination. To indicate that searching without any search parameters is allowed, an empty string must be included in the list. Including an asterisk, `*`, in the list indicates support of any search parameter combination. For resources without the property, the value of `fhirServer/resources/Resource/searchParameterCombinations` is used.| |`fhirServer/notifications/common/includeResourceTypes`|string list|A comma-separated list of resource types for which notification event messages should be published.| |`fhirServer/notifications/websocket/enabled`|boolean|A boolean flag which indicates whether or not websocket notifications are enabled.| |`fhirServer/notifications/kafka/enabled`|boolean|A boolean flag which indicates whether or not kafka notifications are enabled.| @@ -1604,9 +1610,15 @@ This section contains reference information about each of the configuration prop |`fhirServer/resources/open`|true| |`fhirServer/resources/Resource/interactions`|null (all interactions supported)| |`fhirServer/resources/Resource/searchParameters`|null (all global search parameters supported)| +|`fhirServer/resources/Resource/searchIncludes`|null (all \_include values supported)| +|`fhirServer/resources/Resource/searchRevIncludes`|null (all \_revinclude values supported)| +|`fhirServer/resources/Resource/searchParameterCombinations`|null (all search parameter combinations supported)| |`fhirServer/resources//interactions`|null (inherets from `fhirServer/resources/Resource/interactions`)| |`fhirServer/resources//searchParameters`|null (all type-specific search parameters supported)| |`fhirServer/resources//searchParameters/`|null| +|`fhirServer/resources//searchIncludes`|null (inherets from `fhirServer/resources/Resource/searchIncludes`)| +|`fhirServer/resources//searchRevIncludes`|null (inherets from `fhirServer/resources/Resource/searchRevIncludes`)| +|`fhirServer/resources//searchParameterCombinations`|null (inherets from `fhirServer/resources/Resource/searchParameterCombinations`)| |`fhirServer/notifications/common/includeResourceTypes`|`["*"]`| |`fhirServer/notifications/websocket/enabled`|false| |`fhirServer/notifications/kafka/enabled`|false| @@ -1680,9 +1692,15 @@ must restart the server for that change to take effect. |`fhirServer/resources/Resource/interactions`|Y|Y| |`fhirServer/resources/Resource/searchParameters`|Y|Y| |`fhirServer/resources/Resource/searchParameters/`|Y|Y| +|`fhirServer/resources/Resource/searchIncludes`|Y|Y| +|`fhirServer/resources/Resource/searchRevIncludes`|Y|Y| +|`fhirServer/resources/Resource/searchParametersCombinations`|Y|Y| |`fhirServer/resources//interactions`|Y|Y| |`fhirServer/resources//searchParameters`|Y|Y| |`fhirServer/resources//searchParameters/`|Y|Y| +|`fhirServer/resources//searchIncludes`|Y|Y| +|`fhirServer/resources//searchRevIncludes`|Y|Y| +|`fhirServer/resources//searchParametersCombinations`|Y|Y| |`fhirServer/notifications/common/includeResourceTypes`|N|N| |`fhirServer/notifications/websocket/enabled`|N|N| |`fhirServer/notifications/kafka/enabled`|N|N| diff --git a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java index da56d09fc73..3bc440fc9c1 100644 --- a/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java +++ b/fhir-config/src/main/java/com/ibm/fhir/config/FHIRConfiguration.java @@ -43,6 +43,7 @@ public class FHIRConfiguration { public static final String PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETERS = "searchParameters"; public static final String PROPERTY_FIELD_RESOURCES_SEARCH_INCLUDES = "searchIncludes"; public static final String PROPERTY_FIELD_RESOURCES_SEARCH_REV_INCLUDES = "searchRevIncludes"; + public static final String PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETER_COMBINATIONS = "searchParameterCombinations"; // Auth and security properties public static final String PROPERTY_SECURITY_CORS = "fhirServer/security/cors"; diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/exception/SearchExceptionUtil.java b/fhir-search/src/main/java/com/ibm/fhir/search/exception/SearchExceptionUtil.java index c49c6098ed0..fa9a814dd05 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/exception/SearchExceptionUtil.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/exception/SearchExceptionUtil.java @@ -18,16 +18,17 @@ public class SearchExceptionUtil { private static final String ILLEGAL_EXCEPTION = "SearchParameter filter property values must be an array of String."; private static final String ILLEGAL_ARGUMENT_EXCEPTION = "No constant with value '%s' found."; private static final String PARSE_PARAMETER_EXCEPTION = "An error occurred while parsing parameter '%s'."; + private static final String PARSE_PARAMETERS_EXCEPTION = "An error occurred while parsing parameters."; private static final String CHAINED_PARAMETER_EXCEPTION = "Unable to parse chained parameter: '%s'"; private static final String BADFORMAT_EXCEPTION = "Invalid Date Time Format found please use 'yyyy-mm-ddThh:mm:ss[Z|(+|-)hh:mm].'"; - + private SearchExceptionUtil() { // No Op } /** * creates an invalid search exception. - * + * * @param msg * @return */ @@ -38,7 +39,7 @@ public static FHIRSearchException buildNewInvalidSearchException(final String ms /** * creates a new parse parameter exception - * + * * @param name * @param e * @return @@ -49,9 +50,22 @@ public static FHIRSearchException buildNewParseParameterException(final String n return new FHIRSearchException(msg, e).withIssue(ooi); } + /** + * creates a new parse parameters exception + * + * @param name + * @param e + * @return + */ + public static FHIRSearchException buildNewParseParametersException(Exception e) { + String msg = String.format(PARSE_PARAMETERS_EXCEPTION); + OperationOutcome.Issue ooi = FHIRUtil.buildOperationOutcomeIssue(msg, IssueType.INVALID); + return new FHIRSearchException(msg, e).withIssue(ooi); + } + /** * creates a new chained parameter exception - * + * * @param name * @param e * @return @@ -64,7 +78,7 @@ public static FHIRSearchException buildNewChainedParameterException(final String /** * builds an illegal state exception for a search filter execution - * + * * @return */ public static IllegalStateException buildNewIllegalStateException() { @@ -73,17 +87,17 @@ public static IllegalStateException buildNewIllegalStateException() { /** * builds an illegal Argument exception. - * + * * @param val * @return */ public static IllegalArgumentException buildNewIllegalArgumentException(final String val) { return new IllegalArgumentException(String.format(ILLEGAL_ARGUMENT_EXCEPTION, val)); } - + /** * build data time format exception - * + * * @param exception e * @return */ diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java index 44829a1ab86..41e56e0bbbd 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java @@ -100,6 +100,10 @@ public class SearchUtil { private static final String DIFFERENT_MODIFIYERRESOURCETYPES_FOUND_FOR_RESOURCETYPES = "Different Modifier resource types are found for search parameter [%s] of the to-be-searched resource types."; + // Other Constants + private static final String SEARCH_PARAM_COMBINATION_ANY = "*"; + private static final String SEARCH_PARAM_COMBINATION_DELIMITER = "\\+"; + // The functionality is split into a new class. private static final Sort sort = new Sort(); @@ -816,6 +820,16 @@ public static FHIRSearchContext parseQueryParameters(Class resourceType, } } // end for + try { + // Check for valid search parameter combinations + checkSearchParameterCombinations(resourceType, parameters); + + } catch (FHIRSearchException se) { + throw se; + } catch (Exception e) { + throw SearchExceptionUtil.buildNewParseParametersException(e); + } + context.setSearchParameters(parameters); return context; } @@ -902,6 +916,109 @@ private static void checkSearchParameterRestrictions(String parameterCode, Searc } } + /** + * Checks that the combination of search parameters is valid. + * + * @param resourceType + * the resource type + * @param parameters + * the query parameters to check + * @throws Exception + * an exception + */ + private static void checkSearchParameterCombinations(Class resourceType, List parameters) + throws Exception { + + List> validCombinations = getSearchParameterCombinations(resourceType.getSimpleName()); + if (validCombinations != null) { + Set searchParameterCodes = parameters.stream().map(qp -> qp.getCode()).collect(Collectors.toSet()); + + // Check that search parameter codes are a valid combinations + if (!validCombinations.contains(searchParameterCodes)) { + String msg; + if (searchParameterCodes.isEmpty()) { + msg = "A valid search parameter combination is required"; + } else { + msg = "Search parameter combination is not valid"; + } + throw SearchExceptionUtil.buildNewInvalidSearchException(msg); + } + } + } + + /** + * Retrieves the search parameter combinations. + * + * @param resourceType + * the resource type + * @return list of allowed search parameter combinations, or null if any search parameter combination is allowed + * @throws Exception + * an exception + */ + private static List> getSearchParameterCombinations(String resourceType) throws Exception { + + List> spCombinations = null; + + // Retrieve the "resources" config property group. + PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES); + if (rsrcsGroup != null) { + List rsrcsEntries = rsrcsGroup.getProperties(); + if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) { + List combinations = null; + + // Try to find search parameter combinations property for matching resource type + for (PropertyEntry rsrcsEntry : rsrcsEntries) { + if (resourceType.equals(rsrcsEntry.getName())) { + PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue(); + if (resourceTypeGroup != null) { + combinations = resourceTypeGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETER_COMBINATIONS); + break; + } + } + } + + // Otherwise, try to find search parameter combinations property for "Resource" resource type + if (combinations == null) { + for (PropertyEntry rsrcsEntry : rsrcsEntries) { + + // Check if matching resource type + if (SearchConstants.RESOURCE_RESOURCE.equals(rsrcsEntry.getName())) { + PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue(); + if (resourceTypeGroup != null) { + combinations = + resourceTypeGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETER_COMBINATIONS); + break; + } + } + } + } + + // Convert the delimited combinations to a list of sets + if (combinations != null) { + spCombinations = new ArrayList<>(); + for (String combination : combinations) { + Set combinationSet = new HashSet<>(); + if (!combination.isEmpty()) { + // If any search parameter combination is allowed, return null + if (SEARCH_PARAM_COMBINATION_ANY.equals(combination)) { + return null; + } + for (String spString : combination.split(SEARCH_PARAM_COMBINATION_DELIMITER)) { + if (spString.trim().isEmpty()) { + throw SearchExceptionUtil.buildNewIllegalStateException(); + } + combinationSet.add(spString); + } + } + spCombinations.add(combinationSet); + } + } + } + } + + return spCombinations; + } + /** * Common logic from handling a single queryParameterValueString based on its type */ @@ -1650,7 +1767,7 @@ private static List getSearchIncludeRestrictions(String resourceType) th List rsrcsEntries = rsrcsGroup.getProperties(); if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) { - // Try find search includes property for matching resource type + // Try to find search includes property for matching resource type for (PropertyEntry rsrcsEntry : rsrcsEntries) { if (resourceType.equals(rsrcsEntry.getName())) { PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue(); @@ -1660,7 +1777,7 @@ private static List getSearchIncludeRestrictions(String resourceType) th } } - // Otherwise, try find search includes property for "Resource" resource type + // Otherwise, try to find search includes property for "Resource" resource type for (PropertyEntry rsrcsEntry : rsrcsEntries) { // Check if matching resource type @@ -1671,7 +1788,6 @@ private static List getSearchIncludeRestrictions(String resourceType) th } } } - } } diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/SearchParameterRestrictionTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/SearchParameterRestrictionTest.java index d044ae5c245..514883a483a 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/parameters/SearchParameterRestrictionTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/parameters/SearchParameterRestrictionTest.java @@ -17,11 +17,13 @@ import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.FHIRRequestContext; +import com.ibm.fhir.model.resource.CarePlan; import com.ibm.fhir.model.resource.ExplanationOfBenefit; import com.ibm.fhir.model.resource.MedicationRequest; import com.ibm.fhir.model.resource.Organization; import com.ibm.fhir.model.resource.Patient; import com.ibm.fhir.model.resource.Person; +import com.ibm.fhir.model.resource.Practitioner; import com.ibm.fhir.model.resource.RelatedPerson; import com.ibm.fhir.search.exception.FHIRSearchException; import com.ibm.fhir.search.test.BaseSearchTest; @@ -34,7 +36,7 @@ public class SearchParameterRestrictionTest extends BaseSearchTest { private static final String DEFAULT_TENANT_ID = "default"; private static final String TENANT_ID = "tenant7"; - + @Override @BeforeClass public void setup() { @@ -47,37 +49,37 @@ public void testMultipleOrAllowed() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("multiple-birth-count", Collections.singletonList("eq2,eq3,eq4")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test(expectedExceptions = { FHIRSearchException.class }) public void testMultipleOrDisllowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("multiple-birth-count-basic", Collections.singletonList("eq2,eq3")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test public void testMultipleAndAllowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("multiple-birth-count", Arrays.asList("eq2","eq3")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test(expectedExceptions = { FHIRSearchException.class }) public void testMultipleAndDisallowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("multiple-birth-count-basic", Arrays.asList("eq2","eq3")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } @@ -100,7 +102,7 @@ public void testComparatorDisallowed() throws Exception { SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test public void testOtherComparatorDisallowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); @@ -117,7 +119,7 @@ public void testModifierAllowed() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("multiple-birth-count:missing", Collections.singletonList("true")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } @@ -130,54 +132,54 @@ public void testModifierDisallowed() throws Exception { SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test public void testIncludeAllowedByDefault() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(DEFAULT_TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_include", Collections.singletonList("Person:organization")); - + SearchUtil.parseQueryParameters(Person.class, queryParameters); } - + @Test public void testIncludeAllowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_include", Collections.singletonList("Patient:general-practitioner")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test public void testIncludeWildcardAllowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_include", Collections.singletonList("ExplanationOfBenefit:*")); - + SearchUtil.parseQueryParameters(ExplanationOfBenefit.class, queryParameters); } - + @Test(expectedExceptions = { FHIRSearchException.class }) public void testIncludeWildcardNotAllowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_include", Collections.singletonList("Patient:*")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test public void testIncludeAllowedByBaseResource() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_include", Collections.singletonList("MedicationRequest:patient")); - + SearchUtil.parseQueryParameters(MedicationRequest.class, queryParameters); } @@ -207,27 +209,27 @@ public void testRevIncludeAllowedByDefault() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_revinclude", Collections.singletonList("Person:organization")); - + SearchUtil.parseQueryParameters(Organization.class, queryParameters); } - + @Test public void testRevIncludeAllowed() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_revinclude", Collections.singletonList("MedicationRequest:intended-performer")); - + SearchUtil.parseQueryParameters(Patient.class, queryParameters); } - + @Test public void testRevIncludeAllowedByBaseResource() throws Exception { FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); Map> queryParameters = new HashMap<>(); queryParameters.put("_revinclude", Collections.singletonList("Provenance:target")); - + SearchUtil.parseQueryParameters(Person.class, queryParameters); } @@ -248,6 +250,107 @@ public void testRevIncludeDisallowedByBaseResource() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_revinclude", Collections.singletonList("MedicationRequest:intended-performer")); + SearchUtil.parseQueryParameters(Practitioner.class, queryParameters); + } + + @Test + public void testEmptySearchParamAllowedByDefault() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(DEFAULT_TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + + SearchUtil.parseQueryParameters(Patient.class, queryParameters); + } + + @Test + public void testEmptySearchParamAllowedByBaseResource() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + + SearchUtil.parseQueryParameters(ExplanationOfBenefit.class, queryParameters); + } + + @Test + public void testEmptySearchParamAllowed() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + + SearchUtil.parseQueryParameters(Patient.class, queryParameters); + } + + @Test(expectedExceptions = { FHIRSearchException.class }) + public void testEmptySearchParamDisallowed() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + + SearchUtil.parseQueryParameters(CarePlan.class, queryParameters); + } + + @Test + public void testSearchParamCombinationAllowedByDefault() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(DEFAULT_TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + queryParameters.put("_id", Collections.singletonList("abcd-1234")); + queryParameters.put("active", Collections.singletonList("true")); + + SearchUtil.parseQueryParameters(Patient.class, queryParameters); + } + + @Test + public void testSearchParamCombinationAllowedByBaseResource() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + queryParameters.put("_id", Collections.singletonList("abcd-1234")); + + SearchUtil.parseQueryParameters(ExplanationOfBenefit.class, queryParameters); + } + + @Test + public void testSearchParamCombinationAllowed() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + queryParameters.put("patient", Collections.singletonList("Patient/abcd-1234")); + queryParameters.put("category", Collections.singletonList("system|code")); + + SearchUtil.parseQueryParameters(CarePlan.class, queryParameters); + } + + @Test + public void testAnySearchParamCombinationAllowed() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + queryParameters.put("_id", Collections.singletonList("abcd-1234")); + queryParameters.put("active", Collections.singletonList("true")); + SearchUtil.parseQueryParameters(RelatedPerson.class, queryParameters); } + + @Test(expectedExceptions = { FHIRSearchException.class }) + public void testSearchParamCombinationDisallowedByBaseResource() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + queryParameters.put("status", Collections.singletonList("active")); + + SearchUtil.parseQueryParameters(ExplanationOfBenefit.class, queryParameters); + } + + @Test(expectedExceptions = { FHIRSearchException.class }) + public void testSearchParamCombinationDisallowed() throws Exception { + FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID)); + + Map> queryParameters = new HashMap<>(); + queryParameters.put("_id", Collections.singletonList("abcd-1234")); + queryParameters.put("active", Collections.singletonList("true")); + + SearchUtil.parseQueryParameters(Patient.class, queryParameters); + } + } diff --git a/fhir-search/src/test/resources/config/tenant7/fhir-server-config.json b/fhir-search/src/test/resources/config/tenant7/fhir-server-config.json index 59e1a133b77..27de13bf549 100644 --- a/fhir-search/src/test/resources/config/tenant7/fhir-server-config.json +++ b/fhir-search/src/test/resources/config/tenant7/fhir-server-config.json @@ -3,6 +3,12 @@ "fhirServer": { "resources": { "open": true, + "CarePlan": { + "searchParameterCombinations": ["patient+category+status+date", + "patient+category+status", + "patient+category", + "patient+category+date"] + }, "ExplanationOfBenefit": { "interactions": [ "read", @@ -28,14 +34,19 @@ "service-date": "http://hl7.org/fhir/us/carin-bb/SearchParameter/explanationofbenefit-service-date" } }, - "Patient": { - "searchIncludes": ["Patient:general-practitioner"], - "searchRevIncludes": ["MedicationRequest:intended-performer"] + "Patient": { + "searchIncludes": ["Patient:general-practitioner"], + "searchRevIncludes": ["MedicationRequest:intended-performer"], + "searchParameterCombinations": ["","_id","multiple-birth-count","multiple-birth-count-basic"] + }, + "RelatedPerson": { + "searchParameterCombinations": ["*"] }, - "Resource": { - "searchIncludes": ["MedicationRequest:patient"], - "searchRevIncludes": ["Provenance:target"] - } + "Resource": { + "searchIncludes": ["MedicationRequest:patient"], + "searchRevIncludes": ["Provenance:target"], + "searchParameterCombinations": ["","_id"] + } } } }