From f9e55b5d8a4cd2370ece9affe0994357acb194b5 Mon Sep 17 00:00:00 2001 From: birariro Date: Fri, 21 Jun 2024 13:27:36 +0900 Subject: [PATCH 1/5] fix: remove last slash of url --- .../cloud/openfeign/FeignClientsRegistrar.java | 3 +++ .../cloud/openfeign/FeignClientBuilderTests.java | 4 ++-- .../cloud/openfeign/FeignClientsRegistrarTests.java | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java index 03149a512..00fe8f195 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java @@ -118,6 +118,9 @@ static String getUrl(String url) { if (!url.contains("://")) { url = "http://" + url; } + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } try { new URL(url); } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java index 4f876c4da..67a9e85bd 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java @@ -131,7 +131,7 @@ void forType_allFieldsSetOnBuilder() { assertFactoryBeanField(builder, "contextId", "TestContext"); // and: - assertFactoryBeanField(builder, "url", "http://Url/"); + assertFactoryBeanField(builder, "url", "http://Url"); assertFactoryBeanField(builder, "path", "/Path"); assertFactoryBeanField(builder, "dismiss404", true); @@ -155,7 +155,7 @@ void forType_clientFactoryBeanProvided() { assertFactoryBeanField(builder, "contextId", "TestContext"); // and: - assertFactoryBeanField(builder, "url", "http://Url/"); + assertFactoryBeanField(builder, "url", "http://Url"); assertFactoryBeanField(builder, "path", "/Path"); assertFactoryBeanField(builder, "dismiss404", true); List additionalCustomizers = getFactoryBeanField(builder, "additionalCustomizers"); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java index 07f5a25b0..e98326916 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java @@ -89,6 +89,12 @@ private String testGetName(String name) { return registrar.getName(Collections.singletonMap("name", name)); } + @Test + void removeLastSlashOfUrl() { + String url = FeignClientsRegistrar.getUrl("http://localhost/"); + assertThat(url).isEqualTo("http://localhost"); + } + @Test void testFallback() { assertThatExceptionOfType(IllegalArgumentException.class) From 8cc00f0060631f74c14f198e3571a98975dc176d Mon Sep 17 00:00:00 2001 From: birariro Date: Sun, 29 Sep 2024 00:36:48 +0900 Subject: [PATCH 2/5] code rollback --- .../springframework/cloud/openfeign/FeignClientsRegistrar.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java index 00fe8f195..03149a512 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java @@ -118,9 +118,6 @@ static String getUrl(String url) { if (!url.contains("://")) { url = "http://" + url; } - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } try { new URL(url); } From 560239a92b7169b8147eb66c0186de91f132d39a Mon Sep 17 00:00:00 2001 From: Artem Gorshkov <47691597+gorshik@users.noreply.github.com> Date: Thu, 28 Apr 2022 12:36:53 +0300 Subject: [PATCH 3/5] Delete '/' at the end of url In situation when we create FeignClient in this way: @FeignClient(url = "https://localhost/", path = "api/v2") We will get an error because of adding '/' in the beginning of the path in FeignClientsRegistrar.getPath, result will: https://localhost//api/v2 To escape this situation we need to delete '/' at the end of url in FeignClientsRegistrar.getUrl --- .../springframework/cloud/openfeign/FeignClientsRegistrar.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java index 03149a512..00fe8f195 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java @@ -118,6 +118,9 @@ static String getUrl(String url) { if (!url.contains("://")) { url = "http://" + url; } + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } try { new URL(url); } From 1c22ebe06b6994c13d057cfe39a1597f8473e7f0 Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Wed, 9 Oct 2024 19:13:51 +0200 Subject: [PATCH 4/5] Handle removing trailing slash from path. --- .../openfeign/FeignClientProperties.java | 21 ++++++++-- .../openfeign/FeignClientsConfiguration.java | 5 +-- .../openfeign/support/SpringMvcContract.java | 38 +++++++++++++++++++ .../openfeign/FeignClientsRegistrarTests.java | 4 +- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java index bec1b653a..02041e60c 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,6 +61,12 @@ public class FeignClientProperties { */ private boolean decodeSlash = true; + /** + * If {@code true}, trailing slashes at the end + * of request urls will be removed. + */ + private boolean removeTrailingSlash; + public boolean isDefaultToProperties() { return defaultToProperties; } @@ -93,6 +99,14 @@ public void setDecodeSlash(boolean decodeSlash) { this.decodeSlash = decodeSlash; } + public boolean isRemoveTrailingSlash() { + return removeTrailingSlash; + } + + public void setRemoveTrailingSlash(boolean removeTrailingSlash) { + this.removeTrailingSlash = removeTrailingSlash; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -103,12 +117,13 @@ public boolean equals(Object o) { } FeignClientProperties that = (FeignClientProperties) o; return defaultToProperties == that.defaultToProperties && Objects.equals(defaultConfig, that.defaultConfig) - && Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash); + && Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash) + && Objects.equals(removeTrailingSlash, that.removeTrailingSlash); } @Override public int hashCode() { - return Objects.hash(defaultToProperties, defaultConfig, config, decodeSlash); + return Objects.hash(defaultToProperties, defaultConfig, config, decodeSlash, removeTrailingSlash); } /** diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java index 3372a0c55..23dfcdfa0 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,8 +145,7 @@ public QueryMapEncoder feignQueryMapEncoderPageable() { @Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { - boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash(); - return new SpringMvcContract(parameterProcessors, feignConversionService, decodeSlash); + return new SpringMvcContract(parameterProcessors, feignConversionService, feignClientProperties); } @Bean diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java index 9d9d7a8c1..2ab0c5d49 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java @@ -41,6 +41,7 @@ import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.CollectionFormat; +import org.springframework.cloud.openfeign.FeignClientProperties; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.cloud.openfeign.annotation.CookieValueParameterProcessor; import org.springframework.cloud.openfeign.annotation.MatrixVariableParameterProcessor; @@ -115,6 +116,8 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource private final boolean decodeSlash; + private final boolean removeTrailingSlash; + public SpringMvcContract() { this(Collections.emptyList()); } @@ -128,8 +131,32 @@ public SpringMvcContract(List annotatedParameterPro this(annotatedParameterProcessors, conversionService, true); } + /** + * Creates a {@link SpringMvcContract} based on annotatedParameterProcessors, + * conversionService and decodeSlash value. + * @param annotatedParameterProcessors list of {@link AnnotatedParameterProcessor} objects used to resolve parameters + * @param conversionService {@link ConversionService} used for type conversion + * @param decodeSlash indicates whether slashes should be decoded + * @deprecated in favour of {@link SpringMvcContract#SpringMvcContract(List, ConversionService, FeignClientProperties)} + */ + @Deprecated public SpringMvcContract(List annotatedParameterProcessors, ConversionService conversionService, boolean decodeSlash) { + this(annotatedParameterProcessors, conversionService, decodeSlash, false); + } + + /** + * Creates a {@link SpringMvcContract} based on annotatedParameterProcessors, + * conversionService and decodeSlash value. + * @param annotatedParameterProcessors list of {@link AnnotatedParameterProcessor} objects used to resolve parameters + * @param conversionService {@link ConversionService} used for type conversion + * @param decodeSlash indicates whether slashes should be decoded + * @param removeTrailingSlash indicates whether trailing slashes should be removed + * @deprecated in favour of {@link SpringMvcContract#SpringMvcContract(List, ConversionService, FeignClientProperties)} + */ + @Deprecated + public SpringMvcContract(List annotatedParameterProcessors, + ConversionService conversionService, boolean decodeSlash, boolean removeTrailingSlash) { Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null."); Assert.notNull(conversionService, "ConversionService can not be null."); @@ -140,6 +167,14 @@ public SpringMvcContract(List annotatedParameterPro this.conversionService = conversionService; convertingExpanderFactory = new ConvertingExpanderFactory(conversionService); this.decodeSlash = decodeSlash; + this.removeTrailingSlash = removeTrailingSlash; + } + + public SpringMvcContract(List annotatedParameterProcessors, + ConversionService conversionService, FeignClientProperties feignClientProperties) { + this(annotatedParameterProcessors, conversionService, + feignClientProperties == null || feignClientProperties.isDecodeSlash(), + feignClientProperties != null && feignClientProperties.isRemoveTrailingSlash()); } private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) { @@ -229,6 +264,9 @@ protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodA if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) { pathValue = "/" + pathValue; } + if (removeTrailingSlash && pathValue.endsWith("/")) { + pathValue = pathValue.substring(0, pathValue.length() - 1); + } data.template().uri(pathValue, true); if (data.template().decodeSlash() != decodeSlash) { data.template().decodeSlash(decodeSlash); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java index e98326916..5459dd662 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ private String testGetName(String name) { } @Test - void removeLastSlashOfUrl() { + void testRemoveTrailingSlashFromUrl() { String url = FeignClientsRegistrar.getUrl("http://localhost/"); assertThat(url).isEqualTo("http://localhost"); } From bf275ec6d93af2e3f3772c16d7360b5b587abf0f Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Thu, 10 Oct 2024 14:37:04 +0200 Subject: [PATCH 5/5] Add docs. Add more tests. Reformat code. --- .../ROOT/pages/spring-cloud-openfeign.adoc | 7 +- docs/modules/ROOT/partials/_configprops.adoc | 1 + .../openfeign/FeignClientProperties.java | 7 +- .../openfeign/support/SpringMvcContract.java | 20 +++--- .../support/SpringMvcContractTests.java | 70 ++++++++++++++++++- 5 files changed, 89 insertions(+), 16 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc b/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc index 60bc386e9..f99960015 100644 --- a/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-openfeign.adoc @@ -288,7 +288,12 @@ public class CustomConfiguration { } ---- -TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `spring.cloud.openfeign.client.decodeSlash` to `false`. +TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `spring.cloud.openfeign.client.decode-slash` to `false`. + + +TIP: By default, Feign clients do not remove trailing slash `/` characters from the request path. +You can change this behaviour, by setting the value of `spring.cloud.openfeign.client.remove-trailing-slash` to `true`. +Trailing slash removal from the request path is going to be made the default behaviour in the next major release. [[springencoder-configuration]] ==== `SpringEncoder` configuration diff --git a/docs/modules/ROOT/partials/_configprops.adoc b/docs/modules/ROOT/partials/_configprops.adoc index 73e1462f3..d5e19961d 100644 --- a/docs/modules/ROOT/partials/_configprops.adoc +++ b/docs/modules/ROOT/partials/_configprops.adoc @@ -73,6 +73,7 @@ |spring.cloud.openfeign.client.default-config | `+++default+++` | |spring.cloud.openfeign.client.default-to-properties | `+++true+++` | |spring.cloud.openfeign.client.refresh-enabled | `+++false+++` | Enables options value refresh capability for Feign. +|spring.cloud.openfeign.client.remove-trailing-slash | `+++false+++` | If {@code true}, trailing slashes at the end of request urls will be removed. |spring.cloud.openfeign.compression.request.content-encoding-types | | The list of content encodings (applicable encodings depend on the used client). |spring.cloud.openfeign.compression.request.enabled | `+++false+++` | Enables the request sent by Feign to be compressed. |spring.cloud.openfeign.compression.request.mime-types | `+++[text/xml, application/xml, application/json]+++` | The list of supported mime types. diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java index 02041e60c..c6c26c017 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java @@ -62,8 +62,7 @@ public class FeignClientProperties { private boolean decodeSlash = true; /** - * If {@code true}, trailing slashes at the end - * of request urls will be removed. + * If {@code true}, trailing slashes at the end of request urls will be removed. */ private boolean removeTrailingSlash; @@ -117,8 +116,8 @@ public boolean equals(Object o) { } FeignClientProperties that = (FeignClientProperties) o; return defaultToProperties == that.defaultToProperties && Objects.equals(defaultConfig, that.defaultConfig) - && Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash) - && Objects.equals(removeTrailingSlash, that.removeTrailingSlash); + && Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash) + && Objects.equals(removeTrailingSlash, that.removeTrailingSlash); } @Override diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java index 2ab0c5d49..f5474e18e 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java @@ -134,10 +134,12 @@ public SpringMvcContract(List annotatedParameterPro /** * Creates a {@link SpringMvcContract} based on annotatedParameterProcessors, * conversionService and decodeSlash value. - * @param annotatedParameterProcessors list of {@link AnnotatedParameterProcessor} objects used to resolve parameters + * @param annotatedParameterProcessors list of {@link AnnotatedParameterProcessor} + * objects used to resolve parameters * @param conversionService {@link ConversionService} used for type conversion * @param decodeSlash indicates whether slashes should be decoded - * @deprecated in favour of {@link SpringMvcContract#SpringMvcContract(List, ConversionService, FeignClientProperties)} + * @deprecated in favour of + * {@link SpringMvcContract#SpringMvcContract(List, ConversionService, FeignClientProperties)} */ @Deprecated public SpringMvcContract(List annotatedParameterProcessors, @@ -148,15 +150,17 @@ public SpringMvcContract(List annotatedParameterPro /** * Creates a {@link SpringMvcContract} based on annotatedParameterProcessors, * conversionService and decodeSlash value. - * @param annotatedParameterProcessors list of {@link AnnotatedParameterProcessor} objects used to resolve parameters + * @param annotatedParameterProcessors list of {@link AnnotatedParameterProcessor} + * objects used to resolve parameters * @param conversionService {@link ConversionService} used for type conversion * @param decodeSlash indicates whether slashes should be decoded * @param removeTrailingSlash indicates whether trailing slashes should be removed - * @deprecated in favour of {@link SpringMvcContract#SpringMvcContract(List, ConversionService, FeignClientProperties)} + * @deprecated in favour of + * {@link SpringMvcContract#SpringMvcContract(List, ConversionService, FeignClientProperties)} */ @Deprecated public SpringMvcContract(List annotatedParameterProcessors, - ConversionService conversionService, boolean decodeSlash, boolean removeTrailingSlash) { + ConversionService conversionService, boolean decodeSlash, boolean removeTrailingSlash) { Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null."); Assert.notNull(conversionService, "ConversionService can not be null."); @@ -171,10 +175,10 @@ public SpringMvcContract(List annotatedParameterPro } public SpringMvcContract(List annotatedParameterProcessors, - ConversionService conversionService, FeignClientProperties feignClientProperties) { + ConversionService conversionService, FeignClientProperties feignClientProperties) { this(annotatedParameterProcessors, conversionService, - feignClientProperties == null || feignClientProperties.isDecodeSlash(), - feignClientProperties != null && feignClientProperties.isRemoveTrailingSlash()); + feignClientProperties == null || feignClientProperties.isDecodeSlash(), + feignClientProperties != null && feignClientProperties.isRemoveTrailingSlash()); } private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java index 02219e2ce..55a3b97d7 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.Test; import org.springframework.cloud.openfeign.CollectionFormat; +import org.springframework.cloud.openfeign.FeignClientProperties; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; @@ -189,8 +190,23 @@ void testProcessAnnotations_SimpleNoPath() throws Exception { } @Test - void testProcessAnnotations_SimplePathIsOnlyASlash() throws Exception { - Method method = TestTemplate_Simple.class.getDeclaredMethod("getSlashPath", String.class); + void testProcessAnnotations_SimplePathIsOnlyASlashWithParam() throws Exception { + Method method = TestTemplate_Simple.class.getDeclaredMethod("getSlashPathWithParam", String.class); + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().url()).isEqualTo("/?id=" + "{id}"); + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.template().headers().get("Accept").iterator().next()) + .isEqualTo(MediaType.APPLICATION_JSON_VALUE); + } + + @Test + void testProcessAnnotations_SimplePathIsOnlyASlashWithParamWithTrailingSlashRemoval() throws Exception { + FeignClientProperties properties = new FeignClientProperties(); + properties.setRemoveTrailingSlash(true); + contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), properties); + Method method = TestTemplate_Simple.class.getDeclaredMethod("getSlashPathWithParam", String.class); + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); assertThat(data.template().url()).isEqualTo("/?id=" + "{id}"); @@ -284,6 +300,48 @@ void testProcessAnnotations_SimplePostMapping() throws Exception { } + @Test + void testProcessAnnotations_SimplePathIsOnlyASlashWithTrailingSlashRemoval() throws Exception { + FeignClientProperties properties = new FeignClientProperties(); + properties.setRemoveTrailingSlash(true); + contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), properties); + Method method = TestTemplate_Simple.class.getDeclaredMethod("getSlashPath"); + + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().url()).isEqualTo("/"); + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.template().headers().get("Accept").iterator().next()) + .isEqualTo(MediaType.APPLICATION_JSON_VALUE); + } + + @Test + void testProcessAnnotations_SimplePathHasTrailingSlash() throws Exception { + Method method = TestTemplate_Simple.class.getDeclaredMethod("getTrailingSlash"); + + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().url()).isEqualTo("/test1/test2/"); + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.template().headers().get("Accept").iterator().next()) + .isEqualTo(MediaType.APPLICATION_JSON_VALUE); + } + + @Test + void testProcessAnnotations_SimplePathHasTrailingSlashWithTrailingSlashRemoval() throws Exception { + FeignClientProperties properties = new FeignClientProperties(); + properties.setRemoveTrailingSlash(true); + contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), properties); + Method method = TestTemplate_Simple.class.getDeclaredMethod("getTrailingSlash"); + + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().url()).isEqualTo("/test1/test2"); + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.template().headers().get("Accept").iterator().next()) + .isEqualTo(MediaType.APPLICATION_JSON_VALUE); + } + @Test void testProcessAnnotationsOnMethod_Advanced() throws Exception { Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest", String.class, String.class, @@ -738,7 +796,13 @@ public interface TestTemplate_Simple { TestObject postMappingTest(@RequestBody TestObject object); @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) - ResponseEntity getSlashPath(@RequestParam("id") String id); + ResponseEntity getSlashPathWithParam(@RequestParam("id") String id); + + @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity getSlashPath(); + + @GetMapping(value = "test1/test2/", produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity getTrailingSlash(); @GetMapping(path = "test", produces = MediaType.APPLICATION_JSON_VALUE) ResponseEntity getTestNoLeadingSlash(@RequestParam("name") String name);