Skip to content

Commit 6dbc442

Browse files
authored
SpringContract supports @RequestPart and @RequestHeader annotation parameters (#1583)
1 parent afba674 commit 6dbc442

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

spring4/src/main/java/feign/spring/SpringContract.java

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
*/
1414
package feign.spring;
1515

16-
import java.lang.annotation.Annotation;
17-
import java.lang.reflect.Parameter;
18-
import java.util.ArrayList;
19-
import java.util.Collection;
20-
import feign.Util;
21-
import org.springframework.web.bind.annotation.*;
2216
import feign.DeclarativeContract;
2317
import feign.MethodMetadata;
2418
import feign.Request;
19+
import feign.Util;
20+
import org.springframework.web.bind.annotation.*;
21+
import java.lang.reflect.Parameter;
22+
import java.lang.reflect.Type;
23+
import java.util.*;
24+
import static feign.Util.checkState;
2525

2626
public class SpringContract extends DeclarativeContract {
2727

@@ -94,6 +94,8 @@ public SpringContract() {
9494
handleProducesAnnotation(data, "application/json");
9595
});
9696
registerParameterAnnotation(RequestParam.class, requestParamParameterAnnotationProcessor());
97+
registerParameterAnnotation(RequestPart.class, requestPartParameterAnnotationProcessor());
98+
registerParameterAnnotation(RequestHeader.class, requestHeaderParameterAnnotationProcessor());
9799
}
98100

99101
private String[] mapping(String[] firstPriority, String[] fallback) {
@@ -136,6 +138,40 @@ private DeclarativeContract.ParameterAnnotationProcessor<RequestParam> requestPa
136138
};
137139
}
138140

141+
private DeclarativeContract.ParameterAnnotationProcessor<RequestHeader> requestHeaderParameterAnnotationProcessor() {
142+
return (parameterAnnotation, data, paramIndex) -> {
143+
Parameter parameter = data.method().getParameters()[paramIndex];
144+
checkState(data.headerMapIndex() == null, "Header map can only be present once.");
145+
if (Map.class.isAssignableFrom(parameter.getType())
146+
|| isUserPojo(parameter.getType())) {
147+
data.headerMapIndex(paramIndex);
148+
return;
149+
}
150+
151+
String name = parameterName(parameterAnnotation.name(), parameterAnnotation.value(),
152+
parameter);
153+
Collection<String> headers = addTemplatedParam(data.template().headers().get(name), name);
154+
data.template().header(name, headers);
155+
nameParam(data, name, paramIndex);
156+
};
157+
}
158+
159+
private DeclarativeContract.ParameterAnnotationProcessor<RequestPart> requestPartParameterAnnotationProcessor() {
160+
return (parameterAnnotation, data, paramIndex) -> {
161+
Parameter parameter = data.method().getParameters()[paramIndex];
162+
String name = parameterName(parameterAnnotation.name(), parameterAnnotation.value(),
163+
parameter);
164+
data.template().methodMetadata().formParams().add(name);
165+
nameParam(data, name, paramIndex);
166+
};
167+
}
168+
169+
private boolean isUserPojo(Type type) {
170+
String typeName = type.toString();
171+
return !typeName.startsWith("class java.");
172+
}
173+
174+
139175
private void appendMappings(MethodMetadata data, String[] mappings) {
140176
for (int i = 0; i < mappings.length; i++) {
141177
String methodAnnotationValue = mappings[i];

spring4/src/test/java/feign/spring/SpringContractTest.java

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
import static org.hamcrest.Matchers.*;
1717
import static org.hamcrest.CoreMatchers.notNullValue;
1818
import static org.hamcrest.MatcherAssert.assertThat;
19+
import feign.Param;
1920
import org.junit.Before;
2021
import org.junit.Rule;
2122
import org.junit.Test;
2223
import org.junit.rules.ExpectedException;
2324
import org.springframework.http.HttpStatus;
2425
import org.springframework.web.bind.annotation.*;
2526
import java.io.IOException;
26-
import java.util.Arrays;
27-
import java.util.MissingResourceException;
27+
import java.util.*;
2828
import feign.Feign;
2929
import feign.Request;
3030
import feign.jackson.JacksonDecoder;
@@ -50,6 +50,10 @@ public void setup() throws IOException {
5050
.noContent(HttpMethod.GET, "/health/1?deep=true")
5151
.noContent(HttpMethod.GET, "/health/1?deep=true&dryRun=true")
5252
.noContent(HttpMethod.GET, "/health/name?deep=true&dryRun=true")
53+
.noContent(HttpMethod.POST, "/health/part/1")
54+
.noContent(HttpMethod.GET, "/health/header")
55+
.noContent(HttpMethod.GET, "/health/header/map")
56+
.noContent(HttpMethod.GET, "/health/header/pojo")
5357
.ok(HttpMethod.GET, "/health/generic", "{}");
5458
resource = Feign.builder()
5559
.contract(new SpringContract())
@@ -73,6 +77,54 @@ public void testWithName() {
7377
mockClient.verifyOne(HttpMethod.GET, "/health/name?deep=true&dryRun=true");
7478
}
7579

80+
@Test
81+
public void testRequestPart() {
82+
resource.checkRequestPart("1", "hello", "6");
83+
84+
final Request request = mockClient.verifyOne(HttpMethod.POST, "/health/part/1");
85+
assertThat(request.requestTemplate().methodMetadata().formParams(),
86+
contains("name1", "grade1"));
87+
}
88+
89+
@Test
90+
public void testRequestHeader() {
91+
resource.checkRequestHeader("hello", "6");
92+
93+
final Request request = mockClient.verifyOne(HttpMethod.GET, "/health/header");
94+
assertThat(request.headers(),
95+
hasEntry("name1", Arrays.asList("hello")));
96+
assertThat(request.headers(),
97+
hasEntry("grade1", Arrays.asList("6")));
98+
}
99+
100+
@Test
101+
public void testRequestHeaderMap() {
102+
Map<String, String> map = new HashMap<>();
103+
map.put("name1", "hello");
104+
map.put("grade1", "6");
105+
resource.checkRequestHeaderMap(map);
106+
107+
final Request request = mockClient.verifyOne(HttpMethod.GET, "/health/header/map");
108+
assertThat(request.headers(),
109+
hasEntry("name1", Arrays.asList("hello")));
110+
assertThat(request.headers(),
111+
hasEntry("grade1", Arrays.asList("6")));
112+
}
113+
114+
@Test
115+
public void testRequestHeaderPojo() {
116+
HeaderMapUserObject object = new HeaderMapUserObject();
117+
object.setName("hello");
118+
object.setGrade("6");
119+
resource.checkRequestHeaderPojo(object);
120+
121+
final Request request = mockClient.verifyOne(HttpMethod.GET, "/health/header/pojo");
122+
assertThat(request.headers(),
123+
hasEntry("name1", Arrays.asList("hello")));
124+
assertThat(request.headers(),
125+
hasEntry("grade1", Arrays.asList("6")));
126+
}
127+
76128
@Test
77129
public void requestParam() {
78130
resource.check("1", true);
@@ -151,6 +203,43 @@ void checkWithName(
151203
@RequestParam(name = "deep", defaultValue = "false") boolean deepCheck,
152204
@RequestParam(name = "dryRun", defaultValue = "false") boolean dryRun);
153205

206+
@RequestMapping(value = "/part/{id}", method = RequestMethod.POST)
207+
void checkRequestPart(@PathVariable(name = "id") String campaignId,
208+
@RequestPart(name = "name1") String name,
209+
@RequestPart(name = "grade1") String grade);
210+
211+
@RequestMapping(value = "/header", method = RequestMethod.GET)
212+
void checkRequestHeader(@RequestHeader(name = "name1") String name,
213+
@RequestHeader(name = "grade1") String grade);
214+
215+
@RequestMapping(value = "/header/map", method = RequestMethod.GET)
216+
void checkRequestHeaderMap(@RequestHeader Map<String, String> headerMap);
217+
218+
@RequestMapping(value = "/header/pojo", method = RequestMethod.GET)
219+
void checkRequestHeaderPojo(@RequestHeader HeaderMapUserObject object);
220+
154221
}
155222

223+
class HeaderMapUserObject {
224+
@Param("name1")
225+
private String name;
226+
@Param("grade1")
227+
private String grade;
228+
229+
public String getName() {
230+
return name;
231+
}
232+
233+
public void setName(String name) {
234+
this.name = name;
235+
}
236+
237+
public String getGrade() {
238+
return grade;
239+
}
240+
241+
public void setGrade(String grade) {
242+
this.grade = grade;
243+
}
244+
}
156245
}

0 commit comments

Comments
 (0)