diff --git a/spring4/src/main/java/feign/spring/SpringContract.java b/spring4/src/main/java/feign/spring/SpringContract.java index 811cadf0c..d62cacedd 100755 --- a/spring4/src/main/java/feign/spring/SpringContract.java +++ b/spring4/src/main/java/feign/spring/SpringContract.java @@ -13,15 +13,15 @@ */ package feign.spring; -import java.lang.annotation.Annotation; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.Collection; -import feign.Util; -import org.springframework.web.bind.annotation.*; import feign.DeclarativeContract; import feign.MethodMetadata; import feign.Request; +import feign.Util; +import org.springframework.web.bind.annotation.*; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.*; +import static feign.Util.checkState; public class SpringContract extends DeclarativeContract { @@ -94,6 +94,8 @@ public SpringContract() { handleProducesAnnotation(data, "application/json"); }); registerParameterAnnotation(RequestParam.class, requestParamParameterAnnotationProcessor()); + registerParameterAnnotation(RequestPart.class, requestPartParameterAnnotationProcessor()); + registerParameterAnnotation(RequestHeader.class, requestHeaderParameterAnnotationProcessor()); } private String[] mapping(String[] firstPriority, String[] fallback) { @@ -136,6 +138,40 @@ private DeclarativeContract.ParameterAnnotationProcessor requestPa }; } + private DeclarativeContract.ParameterAnnotationProcessor requestHeaderParameterAnnotationProcessor() { + return (parameterAnnotation, data, paramIndex) -> { + Parameter parameter = data.method().getParameters()[paramIndex]; + checkState(data.headerMapIndex() == null, "Header map can only be present once."); + if (Map.class.isAssignableFrom(parameter.getType()) + || isUserPojo(parameter.getType())) { + data.headerMapIndex(paramIndex); + return; + } + + String name = parameterName(parameterAnnotation.name(), parameterAnnotation.value(), + parameter); + Collection headers = addTemplatedParam(data.template().headers().get(name), name); + data.template().header(name, headers); + nameParam(data, name, paramIndex); + }; + } + + private DeclarativeContract.ParameterAnnotationProcessor requestPartParameterAnnotationProcessor() { + return (parameterAnnotation, data, paramIndex) -> { + Parameter parameter = data.method().getParameters()[paramIndex]; + String name = parameterName(parameterAnnotation.name(), parameterAnnotation.value(), + parameter); + data.template().methodMetadata().formParams().add(name); + nameParam(data, name, paramIndex); + }; + } + + private boolean isUserPojo(Type type) { + String typeName = type.toString(); + return !typeName.startsWith("class java."); + } + + private void appendMappings(MethodMetadata data, String[] mappings) { for (int i = 0; i < mappings.length; i++) { String methodAnnotationValue = mappings[i]; diff --git a/spring4/src/test/java/feign/spring/SpringContractTest.java b/spring4/src/test/java/feign/spring/SpringContractTest.java index 9fa7301f0..2acaee2eb 100755 --- a/spring4/src/test/java/feign/spring/SpringContractTest.java +++ b/spring4/src/test/java/feign/spring/SpringContractTest.java @@ -16,6 +16,7 @@ import static org.hamcrest.Matchers.*; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import feign.Param; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -23,8 +24,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.io.IOException; -import java.util.Arrays; -import java.util.MissingResourceException; +import java.util.*; import feign.Feign; import feign.Request; import feign.jackson.JacksonDecoder; @@ -50,6 +50,10 @@ public void setup() throws IOException { .noContent(HttpMethod.GET, "/health/1?deep=true") .noContent(HttpMethod.GET, "/health/1?deep=true&dryRun=true") .noContent(HttpMethod.GET, "/health/name?deep=true&dryRun=true") + .noContent(HttpMethod.POST, "/health/part/1") + .noContent(HttpMethod.GET, "/health/header") + .noContent(HttpMethod.GET, "/health/header/map") + .noContent(HttpMethod.GET, "/health/header/pojo") .ok(HttpMethod.GET, "/health/generic", "{}"); resource = Feign.builder() .contract(new SpringContract()) @@ -73,6 +77,54 @@ public void testWithName() { mockClient.verifyOne(HttpMethod.GET, "/health/name?deep=true&dryRun=true"); } + @Test + public void testRequestPart() { + resource.checkRequestPart("1", "hello", "6"); + + final Request request = mockClient.verifyOne(HttpMethod.POST, "/health/part/1"); + assertThat(request.requestTemplate().methodMetadata().formParams(), + contains("name1", "grade1")); + } + + @Test + public void testRequestHeader() { + resource.checkRequestHeader("hello", "6"); + + final Request request = mockClient.verifyOne(HttpMethod.GET, "/health/header"); + assertThat(request.headers(), + hasEntry("name1", Arrays.asList("hello"))); + assertThat(request.headers(), + hasEntry("grade1", Arrays.asList("6"))); + } + + @Test + public void testRequestHeaderMap() { + Map map = new HashMap<>(); + map.put("name1", "hello"); + map.put("grade1", "6"); + resource.checkRequestHeaderMap(map); + + final Request request = mockClient.verifyOne(HttpMethod.GET, "/health/header/map"); + assertThat(request.headers(), + hasEntry("name1", Arrays.asList("hello"))); + assertThat(request.headers(), + hasEntry("grade1", Arrays.asList("6"))); + } + + @Test + public void testRequestHeaderPojo() { + HeaderMapUserObject object = new HeaderMapUserObject(); + object.setName("hello"); + object.setGrade("6"); + resource.checkRequestHeaderPojo(object); + + final Request request = mockClient.verifyOne(HttpMethod.GET, "/health/header/pojo"); + assertThat(request.headers(), + hasEntry("name1", Arrays.asList("hello"))); + assertThat(request.headers(), + hasEntry("grade1", Arrays.asList("6"))); + } + @Test public void requestParam() { resource.check("1", true); @@ -151,6 +203,43 @@ void checkWithName( @RequestParam(name = "deep", defaultValue = "false") boolean deepCheck, @RequestParam(name = "dryRun", defaultValue = "false") boolean dryRun); + @RequestMapping(value = "/part/{id}", method = RequestMethod.POST) + void checkRequestPart(@PathVariable(name = "id") String campaignId, + @RequestPart(name = "name1") String name, + @RequestPart(name = "grade1") String grade); + + @RequestMapping(value = "/header", method = RequestMethod.GET) + void checkRequestHeader(@RequestHeader(name = "name1") String name, + @RequestHeader(name = "grade1") String grade); + + @RequestMapping(value = "/header/map", method = RequestMethod.GET) + void checkRequestHeaderMap(@RequestHeader Map headerMap); + + @RequestMapping(value = "/header/pojo", method = RequestMethod.GET) + void checkRequestHeaderPojo(@RequestHeader HeaderMapUserObject object); + } + class HeaderMapUserObject { + @Param("name1") + private String name; + @Param("grade1") + private String grade; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGrade() { + return grade; + } + + public void setGrade(String grade) { + this.grade = grade; + } + } }