Skip to content

Commit bfa6385

Browse files
author
kssumin
committed
Add support for negated parameters in SpringMvcContract
- Added 'allowNegatedParams' configuration option to FeignClientProperties - Modified SpringMvcContract to skip negated parameters when the feature is enabled - Added tests for both enabled and disabled cases - Updated documentation with usage examples
1 parent fc42811 commit bfa6385

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

docs/modules/ROOT/pages/spring-cloud-openfeign.adoc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,38 @@ The params attribute also supports the use of multiple `key=value` or only one `
760760
- When `params = { "key1=v1", "key2=v2" }`, the request url is parsed as `/stores/storeId?key1=v1&key2=v2`.
761761
- When `params = "key"`, the request url is parsed as `/stores/storeId?key`.
762762

763+
[[negated-params-support]]
764+
=== Negated Parameters Support
763765

766+
Spring Cloud OpenFeign provides optional support for negated parameters in `@RequestMapping` annotations. By default, negated parameters (such as `params = {"!expiration"}` or `params = {"param!=value"}`) are not supported in Feign clients, resulting in an `IllegalArgumentException`.
767+
768+
When sharing interfaces between server and client, negated parameters can be useful for disambiguation of request mappings on the server side. To allow the use of negated parameters in your Feign clients, set the following property:
769+
770+
[source,yaml]
771+
----
772+
spring:
773+
cloud:
774+
openfeign:
775+
client:
776+
config:
777+
feignName:
778+
allowNegatedParams: true
779+
----
780+
781+
When enabled, negated parameters will be silently ignored when creating the client request. This is the expected behavior since negated parameters are constraints used only on the server side to avoid ambiguous mappings.
782+
783+
Global configuration is also supported:
784+
785+
[source,yaml]
786+
----
787+
spring:
788+
cloud:
789+
openfeign:
790+
client:
791+
config:
792+
default:
793+
allowNegatedParams: true
794+
----
764795

765796
[[feign-querymap-support]]
766797
=== Feign @QueryMap support

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @author Hyeonmin Park
4646
* @author Jasbir Singh
4747
* @author Dominique Villard
48+
* @author kssumin
4849
*/
4950
@ConfigurationProperties("spring.cloud.openfeign.client")
5051
public class FeignClientProperties {
@@ -66,6 +67,11 @@ public class FeignClientProperties {
6667
*/
6768
private boolean removeTrailingSlash;
6869

70+
/**
71+
* If {@code true}, negated parameters (those starting with '!') will be allowed.
72+
*/
73+
private boolean allowNegatedParams = false;
74+
6975
public boolean isDefaultToProperties() {
7076
return defaultToProperties;
7177
}
@@ -106,6 +112,14 @@ public void setRemoveTrailingSlash(boolean removeTrailingSlash) {
106112
this.removeTrailingSlash = removeTrailingSlash;
107113
}
108114

115+
public void setAllowNegatedParams(Boolean allowNegatedParams) {
116+
this.allowNegatedParams = allowNegatedParams;
117+
}
118+
119+
public boolean getAllowNegatedParams() {
120+
return allowNegatedParams;
121+
}
122+
109123
@Override
110124
public boolean equals(Object o) {
111125
if (this == o) {

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
* @author Ram Anaswara
9090
* @author Sam Kruglov
9191
* @author Tang Xiong
92+
* @author kssumin
9293
*/
9394
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
9495

@@ -118,6 +119,8 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
118119

119120
private final boolean removeTrailingSlash;
120121

122+
private final boolean allowNegatedParams;
123+
121124
public SpringMvcContract() {
122125
this(Collections.emptyList());
123126
}
@@ -172,6 +175,25 @@ public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterPro
172175
convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
173176
this.decodeSlash = decodeSlash;
174177
this.removeTrailingSlash = removeTrailingSlash;
178+
this.allowNegatedParams = false;
179+
}
180+
181+
@Deprecated(forRemoval = true)
182+
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
183+
ConversionService conversionService, boolean decodeSlash, boolean removeTrailingSlash,
184+
boolean allowNegatedParams) {
185+
Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
186+
Assert.notNull(conversionService, "ConversionService can not be null.");
187+
188+
List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
189+
processors.addAll(annotatedParameterProcessors);
190+
191+
annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
192+
this.conversionService = conversionService;
193+
convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
194+
this.decodeSlash = decodeSlash;
195+
this.removeTrailingSlash = removeTrailingSlash;
196+
this.allowNegatedParams = allowNegatedParams;
175197
}
176198

177199
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
@@ -411,7 +433,11 @@ private void parseParams(MethodMetadata data, Method method, RequestMapping meth
411433
data.template().query(resolve(nameValueResolver.getName()), resolve(nameValueResolver.getValue()));
412434
}
413435
else {
414-
throw new IllegalArgumentException("Negated params are not supported: " + param);
436+
// Negated parameter case
437+
if (!this.allowNegatedParams) {
438+
throw new IllegalArgumentException("Negated params are not supported: " + param);
439+
}
440+
// When allowed, negated params are skipped
415441
}
416442
}
417443
}

spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
* @author Sam Kruglov
8585
* @author Bhavya Agrawal
8686
* @author Tang Xiong
87+
* @author kssumin
8788
**/
8889

8990
class SpringMvcContractTests {
@@ -769,6 +770,28 @@ void shouldSetPageableAsBodyWhenQueryMapParamPresent() {
769770
assertThat(data.get(1).bodyIndex()).isEqualTo(0);
770771
}
771772

773+
@Test
774+
void testAllowNegatedParams() throws Exception {
775+
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), true, false, true);
776+
777+
Method method = TestTemplate_ParseParams.class.getDeclaredMethod("notEqualParams");
778+
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
779+
780+
assertThat(data.template().url()).isEqualTo("/test");
781+
assertThat(data.template().method()).isEqualTo("GET");
782+
}
783+
784+
@Test
785+
void testDisallowNegatedParams() throws Exception {
786+
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), true, false, false);
787+
788+
Method method = TestTemplate_ParseParams.class.getDeclaredMethod("notEqualParams");
789+
790+
assertThatIllegalArgumentException().isThrownBy(() -> {
791+
contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
792+
}).withMessageContaining("Negated params are not supported");
793+
}
794+
772795
private ConversionService getConversionService() {
773796
FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean();
774797
conversionServiceFactoryBean.afterPropertiesSet();

0 commit comments

Comments
 (0)