diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 38a180af..5a9306be 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -692,20 +692,21 @@ public interface DemoTemplate { ---- === Feign `CollectionFormat` support -We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation.You can annotate a Feign client method with it by passing the desired `feign.CollectionFormat` as annotation value. +We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation. +You can annotate a Feign client method (or the whole class to affect all methods) with it by passing the desired `feign.CollectionFormat` as annotation value. In the following example, the `CSV` format is used instead of the default `EXPLODED` to process the method. [source,java,indent=0] ---- @FeignClient(name = "demo") - protected interface PageableFeignClient { +protected interface PageableFeignClient { - @CollectionFormat(feign.CollectionFormat.CSV) - @GetMapping(path = "/page") - ResponseEntity performRequest(Pageable page); + @CollectionFormat(feign.CollectionFormat.CSV) + @GetMapping(path = "/page") + ResponseEntity performRequest(Pageable page); - } +} ---- TIP: Set the `CSV` format while sending `Pageable` as a query parameter in order for it to be encoded correctly. diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CollectionFormat.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CollectionFormat.java index d40c4a4b..c06b4d4e 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CollectionFormat.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/CollectionFormat.java @@ -25,9 +25,10 @@ import java.lang.annotation.Target; * Indicates which collection format should be used while processing the annotated method. * * @author Olga Maciaszek-Sharma + * @author Sam Kruglov * @see feign.CollectionFormat */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CollectionFormat { 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 504c9262..70587c96 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 @@ -80,6 +80,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg * @author Artyom Romanenko * @author Darren Foong * @author Ram Anaswara + * @author Sam Kruglov */ public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware { @@ -171,11 +172,11 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource @Override protected void processAnnotationOnClass(MethodMetadata data, Class clz) { if (clz.getInterfaces().length == 0) { - RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class); - if (classAnnotation != null) { + RequestMapping requestMapping = findMergedAnnotation(clz, RequestMapping.class); + if (requestMapping != null) { // Prepend path from class annotation if specified - if (classAnnotation.value().length > 0) { - String pathValue = emptyToNull(classAnnotation.value()[0]); + if (requestMapping.value().length > 0) { + String pathValue = emptyToNull(requestMapping.value()[0]); pathValue = resolve(pathValue); if (!pathValue.startsWith("/")) { pathValue = "/" + pathValue; @@ -186,6 +187,10 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource } } } + CollectionFormat collectionFormat = findMergedAnnotation(clz, CollectionFormat.class); + if (collectionFormat != null) { + data.template().collectionFormat(collectionFormat.value()); + } } } 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 a034481a..fa36481e 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 @@ -62,6 +62,7 @@ import org.springframework.web.multipart.MultipartFile; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; +import static feign.CollectionFormat.CSV; import static feign.CollectionFormat.SSV; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assume.assumeTrue; @@ -73,6 +74,7 @@ import static org.junit.Assume.assumeTrue; * @author Aaron Whiteside * @author Artyom Romanenko * @author Olga Maciaszek-Sharma + * @author Sam Kruglov */ public class SpringMvcContractTests { @@ -302,6 +304,15 @@ public class SpringMvcContractTests { assertThat(data.template().collectionFormat()).isEqualTo(SSV); } + @Test + public void processAnnotationOnClass_CollectionFormat() throws NoSuchMethodException { + Method method = TestTemplate_Advanced.class.getDeclaredMethod("getWithoutCollectionFormat"); + + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().collectionFormat()).isEqualTo(CSV); + } + @Test public void testProcessAnnotations_Advanced() throws Exception { Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest", String.class, String.class, @@ -744,12 +755,16 @@ public class SpringMvcContractTests { @JsonAutoDetect @RequestMapping("/advanced") + @CollectionFormat(CSV) public interface TestTemplate_Advanced { @CollectionFormat(SSV) @GetMapping ResponseEntity getWithCollectionFormat(); + @GetMapping + ResponseEntity getWithoutCollectionFormat(); + @ExceptionHandler @PutMapping(path = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE) ResponseEntity getTest(@RequestHeader("Authorization") String auth, @PathVariable("id") String id,