Skip to content

Commit 56c105d

Browse files
odin-delrioadriancole
authored andcommitted
Adds Feign.Builder.mapAndDecode() (OpenFeign#534)
Adds `Feign.Builder.mapAndDecode()` to allow response preprocessing before decoding it
1 parent 0444e23 commit 56c105d

6 files changed

Lines changed: 116 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### Version 9.5
2+
* Adds `Feign.Builder.mapAndDecode()` to allow response preprocessing before decoding it.
3+
14
### Version 9.4.1
25
* 404 responses are no longer swallowed for `void` return types.
36

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ GitHub github = Feign.builder()
187187
.target(GitHub.class, "https://api.github.com");
188188
```
189189

190+
If you need to pre-process the response before give it to the Decoder, you can use the `mapAndDecode` builder method.
191+
An example use case is dealing with an API that only serves jsonp, you will maybe need to unwrap the jsonp before
192+
send it to the Json decoder of your choice:
193+
194+
```java
195+
JsonpApi jsonpApi = Feign.builder()
196+
.mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
197+
.target(JsonpApi.class, "https://some-jsonp-api.com");
198+
```
199+
190200
### Encoders
191201
The simplest way to send a request body to a server is to define a `POST` method that has a `String` or `byte[]` parameter without any annotations on it. You will likely need to add a `Content-Type` header.
192202

core/src/main/java/feign/Feign.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package feign;
1717

18+
import java.io.IOException;
1819
import java.lang.reflect.Method;
1920
import java.lang.reflect.Type;
2021
import java.util.ArrayList;
@@ -143,6 +144,14 @@ public Builder decoder(Decoder decoder) {
143144
return this;
144145
}
145146

147+
/**
148+
* Allows to map the response before passing it to the decoder.
149+
*/
150+
public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
151+
this.decoder = new ResponseMappingDecoder(mapper, decoder);
152+
return this;
153+
}
154+
146155
/**
147156
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with
148157
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}.
@@ -219,4 +228,20 @@ public Feign build() {
219228
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
220229
}
221230
}
231+
232+
static class ResponseMappingDecoder implements Decoder {
233+
234+
private final ResponseMapper mapper;
235+
private final Decoder delegate;
236+
237+
ResponseMappingDecoder(ResponseMapper mapper, Decoder decoder) {
238+
this.mapper = mapper;
239+
this.delegate = decoder;
240+
}
241+
242+
@Override
243+
public Object decode(Response response, Type type) throws IOException {
244+
return delegate.decode(mapper.map(response, type), type);
245+
}
246+
}
222247
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package feign;
2+
3+
import java.lang.reflect.Type;
4+
5+
/**
6+
* Map function to apply to the response before decoding it.
7+
*
8+
* <pre>{@code
9+
* new ResponseMapper() {
10+
* @Override
11+
* public Response map(Response response, Type type) {
12+
* try {
13+
* return response
14+
* .toBuilder()
15+
* .body(Util.toString(response.body().asReader()).toUpperCase().getBytes())
16+
* .build();
17+
* } catch (IOException e) {
18+
* throw new RuntimeException(e);
19+
* }
20+
* }
21+
* };
22+
* }</pre>
23+
*/
24+
public interface ResponseMapper {
25+
26+
Response map(Response response, Type type);
27+
}

core/src/test/java/feign/FeignTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import java.util.Collection;
2626
import java.util.Collections;
27+
import java.util.HashMap;
2728
import java.util.LinkedHashMap;
2829
import okio.Buffer;
2930
import org.assertj.core.api.Fail;
@@ -50,6 +51,7 @@
5051
import feign.codec.Encoder;
5152
import feign.codec.ErrorDecoder;
5253
import feign.codec.StringDecoder;
54+
import feign.Feign.ResponseMappingDecoder;
5355

5456
import static feign.Util.UTF_8;
5557
import static feign.assertj.MockWebServerAssertions.assertThat;
@@ -706,6 +708,49 @@ public void encodedQueryParam() throws Exception {
706708
.hasPath("/?trim=5.2FSi+");
707709
}
708710

711+
@Test
712+
public void responseMapperIsAppliedBeforeDelegate() throws IOException {
713+
ResponseMappingDecoder decoder = new ResponseMappingDecoder(upperCaseResponseMapper(), new StringDecoder());
714+
String output = (String) decoder.decode(responseWithText("response"), String.class);
715+
716+
assertThat(output).isEqualTo("RESPONSE");
717+
}
718+
719+
private ResponseMapper upperCaseResponseMapper() {
720+
return new ResponseMapper() {
721+
@Override
722+
public Response map(Response response, Type type) {
723+
try {
724+
return response
725+
.toBuilder()
726+
.body(Util.toString(response.body().asReader()).toUpperCase().getBytes())
727+
.build();
728+
} catch (IOException e) {
729+
throw new RuntimeException(e);
730+
}
731+
}
732+
};
733+
}
734+
735+
private Response responseWithText(String text) {
736+
return Response.builder()
737+
.body(text, Util.UTF_8)
738+
.status(200)
739+
.headers(new HashMap<String, Collection<String>>())
740+
.build();
741+
}
742+
743+
@Test
744+
public void mapAndDecodeExecutesMapFunction() {
745+
server.enqueue(new MockResponse().setBody("response!"));
746+
747+
TestInterface api = new Feign.Builder()
748+
.mapAndDecode(upperCaseResponseMapper(), new StringDecoder())
749+
.target(TestInterface.class, "http://localhost:" + server.getPort());
750+
751+
assertEquals(api.post(), "RESPONSE!");
752+
}
753+
709754
interface TestInterface {
710755

711756
@RequestLine("POST /")

hystrix/src/main/java/feign/hystrix/HystrixFeign.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import feign.Logger;
1414
import feign.Request;
1515
import feign.RequestInterceptor;
16+
import feign.ResponseMapper;
1617
import feign.Retryer;
1718
import feign.Target;
1819
import feign.codec.Decoder;
@@ -164,6 +165,11 @@ public Builder decoder(Decoder decoder) {
164165
return (Builder) super.decoder(decoder);
165166
}
166167

168+
@Override
169+
public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
170+
return (Builder) super.mapAndDecode(mapper, decoder);
171+
}
172+
167173
@Override
168174
public Builder decode404() {
169175
return (Builder) super.decode404();

0 commit comments

Comments
 (0)