Skip to content

Commit

Permalink
SpringContract supports @RequestPart and @RequestHeader annotation pa…
Browse files Browse the repository at this point in the history
…rameters (#1583)
  • Loading branch information
mroccyen authored Mar 3, 2022
1 parent afba674 commit 6dbc442
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 8 deletions.
48 changes: 42 additions & 6 deletions spring4/src/main/java/feign/spring/SpringContract.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -136,6 +138,40 @@ private DeclarativeContract.ParameterAnnotationProcessor<RequestParam> requestPa
};
}

private DeclarativeContract.ParameterAnnotationProcessor<RequestHeader> 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<String> headers = addTemplatedParam(data.template().headers().get(name), name);
data.template().header(name, headers);
nameParam(data, name, paramIndex);
};
}

private DeclarativeContract.ParameterAnnotationProcessor<RequestPart> 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];
Expand Down
93 changes: 91 additions & 2 deletions spring4/src/test/java/feign/spring/SpringContractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
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;
import org.junit.rules.ExpectedException;
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;
Expand All @@ -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())
Expand All @@ -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<String, String> 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);
Expand Down Expand Up @@ -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<String, String> 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;
}
}
}

0 comments on commit 6dbc442

Please sign in to comment.