Skip to content

Commit 544820c

Browse files
author
Robert Fink
committed
SynchronousMethodHandler does not wrap exceptions thrown by Decoder#decode in decode404 mode
Removing exception wrapping adds flexibility to the interplay of Decoder and ErrorDecoder: A Decoder can now delegate to an ErrorDecoder and the original ErrorDecoder exception gets thrown rather than a Feign-specific DecodeException. An example use-case is a Jackson/Gson implementation of special 404-handling of Optional<Foo> methods: when the Feign client has decode404() enabled, then the Decoder is in charge of dispatching 404 to Optional#absent where applicable, or to a delegate ErrorDecoder otherwise; consistent exception handling requires that the exception produced by the ErrorDecoder does not wrapped.
1 parent 40b32ec commit 544820c

3 files changed

Lines changed: 28 additions & 1 deletion

File tree

core/src/main/java/feign/SynchronousMethodHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ Object executeAndDecode(RequestTemplate template) throws Throwable {
121121
return decode(response);
122122
}
123123
} else if (decode404 && response.status() == 404) {
124-
return decode(response);
124+
return decoder.decode(response, metadata.returnType());
125125
} else {
126126
throw errorDecoder.decode(metadata.configKey(), response);
127127
}

core/src/main/java/feign/codec/Decoder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919
import java.lang.reflect.Type;
2020

21+
import feign.Feign;
2122
import feign.FeignException;
2223
import feign.Response;
2324
import feign.Util;
@@ -49,6 +50,9 @@
4950
* feign.Target#type() interface} processed by {@link feign.Feign#newInstance(feign.Target)}. When
5051
* writing your implementation of Decoder, ensure you also test parameterized types such as {@code
5152
* List<Foo>}.
53+
* <br/> <h3>Note on exception propagation</h3> Exceptions thrown by {@link Decoder}s get wrapped in
54+
* a {@link DecodeException} unless they are a subclass of {@link FeignException} already, and unless
55+
* the client was configured with {@link Feign.Builder#decode404()}.
5256
*/
5357
public interface Decoder {
5458

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.Date;
3737
import java.util.List;
3838
import java.util.Map;
39+
import java.util.NoSuchElementException;
3940
import java.util.concurrent.atomic.AtomicReference;
4041

4142
import feign.Target.HardCodedTarget;
@@ -387,6 +388,23 @@ public Object decode(Response response, Type type) throws IOException {
387388
api.post();
388389
}
389390

391+
@Test
392+
public void decoderCanThrowUnwrappedExceptionInDecode404Mode() throws Exception {
393+
server.enqueue(new MockResponse().setResponseCode(404));
394+
thrown.expect(NoSuchElementException.class);
395+
396+
TestInterface api = new TestInterfaceBuilder()
397+
.decode404()
398+
.decoder(new Decoder() {
399+
@Override
400+
public Object decode(Response response, Type type) throws IOException {
401+
assertEquals(404, response.status());
402+
throw new NoSuchElementException();
403+
}
404+
}).target("http://localhost:" + server.getPort());
405+
api.post();
406+
}
407+
390408
@Test
391409
public void okIfEncodeRootCauseHasNoMessage() throws Exception {
392410
server.enqueue(new MockResponse().setBody("success!"));
@@ -594,6 +612,11 @@ TestInterfaceBuilder errorDecoder(ErrorDecoder errorDecoder) {
594612
return this;
595613
}
596614

615+
TestInterfaceBuilder decode404() {
616+
delegate.decode404();
617+
return this;
618+
}
619+
597620
TestInterface target(String url) {
598621
return delegate.target(TestInterface.class, url);
599622
}

0 commit comments

Comments
 (0)