Browse Source

All decoders follow rule: if a status is 404 it returns empty or null value (#1597)

* All decoders follow rule: if status 404 it returns empty or null value

* Replace decode404 with dismiss404
pull/1604/head
Witalij Berdinskich 3 years ago committed by GitHub
parent
commit
04a85e6961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      core/src/main/java/feign/AsyncFeign.java
  2. 8
      core/src/main/java/feign/AsyncResponseHandler.java
  3. 29
      core/src/main/java/feign/Feign.java
  4. 12
      core/src/main/java/feign/SynchronousMethodHandler.java
  5. 2
      core/src/main/java/feign/Util.java
  6. 2
      core/src/main/java/feign/codec/Decoder.java
  7. 2
      core/src/main/java/feign/codec/ErrorDecoder.java
  8. 2
      core/src/main/java/feign/codec/StringDecoder.java
  9. 12
      core/src/test/java/feign/AsyncFeignTest.java
  10. 4
      core/src/test/java/feign/FeignBuilderTest.java
  11. 12
      core/src/test/java/feign/FeignTest.java
  12. 12
      core/src/test/java/feign/FeignUnderAsyncTest.java
  13. 2
      core/src/test/java/feign/optionals/OptionalDecoderTests.java
  14. 3
      gson/src/main/java/feign/gson/GsonDecoder.java
  15. 6
      gson/src/test/java/feign/gson/GsonCodecTest.java
  16. 12
      hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java
  17. 5
      hystrix/src/main/java/feign/hystrix/HystrixFeign.java
  18. 3
      jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonDecoder.java
  19. 6
      jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java
  20. 4
      jackson-jr/src/main/java/feign/jackson/jr/JacksonJrDecoder.java
  21. 14
      jackson-jr/src/test/java/feign/jackson/jr/JacksonCodecTest.java
  22. 2
      jackson/src/main/java/feign/jackson/JacksonDecoder.java
  23. 3
      jackson/src/main/java/feign/jackson/JacksonIteratorDecoder.java
  24. 20
      jackson/src/test/java/feign/jackson/JacksonCodecTest.java
  25. 12
      java11/src/test/java/feign/http2client/test/Http2ClientAsyncTest.java
  26. 2
      jaxb/src/main/java/feign/jaxb/JAXBDecoder.java
  27. 6
      jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
  28. 9
      json/src/main/java/feign/json/JsonDecoder.java
  29. 37
      json/src/test/java/feign/json/JsonDecoderTest.java
  30. 2
      reactive/src/test/java/feign/reactive/ReactiveFeignIntegrationTest.java
  31. 3
      sax/src/main/java/feign/sax/SAXDecoder.java
  32. 10
      sax/src/test/java/feign/sax/SAXDecoderTest.java
  33. 7
      soap/src/test/java/feign/soap/SOAPCodecTest.java

15
core/src/main/java/feign/AsyncFeign.java

@ -68,7 +68,7 @@ public abstract class AsyncFeign<C> extends Feign { @@ -68,7 +68,7 @@ public abstract class AsyncFeign<C> extends Feign {
private Decoder decoder = new Decoder.Default();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private boolean decode404;
private boolean dismiss404;
private boolean closeAfterDecode = true;
public AsyncBuilder() {
@ -104,9 +104,18 @@ public abstract class AsyncFeign<C> extends Feign { @@ -104,9 +104,18 @@ public abstract class AsyncFeign<C> extends Feign {
/**
* @see Builder#decode404()
* @deprecated
*/
public AsyncBuilder<C> decode404() {
this.decode404 = true;
this.dismiss404 = true;
return this;
}
/**
* @see Builder#dismiss404()
*/
public AsyncBuilder<C> dismiss404() {
this.dismiss404 = true;
return this;
}
@ -256,7 +265,7 @@ public abstract class AsyncFeign<C> extends Feign { @@ -256,7 +265,7 @@ public abstract class AsyncFeign<C> extends Feign {
asyncBuilder.logger,
asyncBuilder.decoder,
asyncBuilder.errorDecoder,
asyncBuilder.decode404,
asyncBuilder.dismiss404,
asyncBuilder.closeAfterDecode);
asyncBuilder.builder.client(this::stageExecution);

8
core/src/main/java/feign/AsyncResponseHandler.java

@ -37,17 +37,17 @@ class AsyncResponseHandler { @@ -37,17 +37,17 @@ class AsyncResponseHandler {
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
private final boolean decode404;
private final boolean dismiss404;
private final boolean closeAfterDecode;
AsyncResponseHandler(Level logLevel, Logger logger, Decoder decoder, ErrorDecoder errorDecoder,
boolean decode404, boolean closeAfterDecode) {
boolean dismiss404, boolean closeAfterDecode) {
super();
this.logLevel = logLevel;
this.logger = logger;
this.decoder = decoder;
this.errorDecoder = errorDecoder;
this.decode404 = decode404;
this.dismiss404 = dismiss404;
this.closeAfterDecode = closeAfterDecode;
}
@ -88,7 +88,7 @@ class AsyncResponseHandler { @@ -88,7 +88,7 @@ class AsyncResponseHandler {
shouldClose = closeAfterDecode;
resultFuture.complete(result);
}
} else if (decode404 && response.status() == 404 && !isVoidType(returnType)) {
} else if (dismiss404 && response.status() == 404 && !isVoidType(returnType)) {
final Object result = decode(response, returnType);
shouldClose = closeAfterDecode;
resultFuture.complete(result);

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

@ -110,7 +110,7 @@ public abstract class Feign { @@ -110,7 +110,7 @@ public abstract class Feign {
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean dismiss404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
private boolean forceDecoding = false;
@ -180,9 +180,32 @@ public abstract class Feign { @@ -180,9 +180,32 @@ public abstract class Feign {
* custom {@link #client(Client) client}.
*
* @since 8.12
* @deprecated
*/
public Builder decode404() {
this.decode404 = true;
this.dismiss404 = true;
return this;
}
/**
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}.
*
* <p/>
* All first-party (ex gson) decoders return well-known empty values defined by
* {@link Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder)
* decoder} or make your own.
*
* <p/>
* This flag only works with 404, as opposed to all or arbitrary status codes. This was an
* explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or
* fallback policy. If your server returns a different status for not-found, correct via a
* custom {@link #client(Client) client}.
*
* @since 11.9
*/
public Builder dismiss404() {
this.dismiss404 = true;
return this;
}
@ -285,7 +308,7 @@ public abstract class Feign { @@ -285,7 +308,7 @@ public abstract class Feign {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
logLevel, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);

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

@ -51,7 +51,7 @@ final class SynchronousMethodHandler implements MethodHandler { @@ -51,7 +51,7 @@ final class SynchronousMethodHandler implements MethodHandler {
List<RequestInterceptor> requestInterceptors, Logger logger,
Logger.Level logLevel, MethodMetadata metadata,
RequestTemplate.Factory buildTemplateFromArgs, Options options,
Decoder decoder, ErrorDecoder errorDecoder, boolean decode404,
Decoder decoder, ErrorDecoder errorDecoder, boolean dismiss404,
boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy,
boolean forceDecoding) {
@ -75,7 +75,7 @@ final class SynchronousMethodHandler implements MethodHandler { @@ -75,7 +75,7 @@ final class SynchronousMethodHandler implements MethodHandler {
} else {
this.decoder = null;
this.asyncResponseHandler = new AsyncResponseHandler(logLevel, logger, decoder, errorDecoder,
decode404, closeAfterDecode);
dismiss404, closeAfterDecode);
}
}
@ -181,20 +181,20 @@ final class SynchronousMethodHandler implements MethodHandler { @@ -181,20 +181,20 @@ final class SynchronousMethodHandler implements MethodHandler {
private final List<RequestInterceptor> requestInterceptors;
private final Logger logger;
private final Logger.Level logLevel;
private final boolean decode404;
private final boolean dismiss404;
private final boolean closeAfterDecode;
private final ExceptionPropagationPolicy propagationPolicy;
private final boolean forceDecoding;
Factory(Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors,
Logger logger, Logger.Level logLevel, boolean decode404, boolean closeAfterDecode,
Logger logger, Logger.Level logLevel, boolean dismiss404, boolean closeAfterDecode,
ExceptionPropagationPolicy propagationPolicy, boolean forceDecoding) {
this.client = checkNotNull(client, "client");
this.retryer = checkNotNull(retryer, "retryer");
this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors");
this.logger = checkNotNull(logger, "logger");
this.logLevel = checkNotNull(logLevel, "logLevel");
this.decode404 = decode404;
this.dismiss404 = dismiss404;
this.closeAfterDecode = closeAfterDecode;
this.propagationPolicy = propagationPolicy;
this.forceDecoding = forceDecoding;
@ -208,7 +208,7 @@ final class SynchronousMethodHandler implements MethodHandler { @@ -208,7 +208,7 @@ final class SynchronousMethodHandler implements MethodHandler {
ErrorDecoder errorDecoder) {
return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
logLevel, md, buildTemplateFromArgs, options, decoder,
errorDecoder, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
errorDecoder, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding);
}
}
}

2
core/src/main/java/feign/Util.java

@ -241,7 +241,7 @@ public class Util { @@ -241,7 +241,7 @@ public class Util {
* </ul>
*
* <p/>
* When {@link Feign.Builder#decode404() decoding HTTP 404 status}, you'll need to teach decoders
* When {@link Feign.Builder#dismiss404() decoding HTTP 404 status}, you'll need to teach decoders
* a default empty value for a type. This method cheaply supports typical types by only looking at
* the raw type (vs type hierarchy). Decorate for sophistication.
*/

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

@ -58,7 +58,7 @@ import feign.Util; @@ -58,7 +58,7 @@ import feign.Util;
* List<Foo>}. <br/>
* <h3>Note on exception propagation</h3> Exceptions thrown by {@link Decoder}s get wrapped in a
* {@link DecodeException} unless they are a subclass of {@link FeignException} already, and unless
* the client was configured with {@link Feign.Builder#decode404()}.
* the client was configured with {@link Feign.Builder#dismiss404()}.
*/
public interface Decoder {

2
core/src/main/java/feign/codec/ErrorDecoder.java

@ -64,7 +64,7 @@ import java.util.Map; @@ -64,7 +64,7 @@ import java.util.Map;
* <p/>
* It is commonly the case that 404 (Not Found) status has semantic value in HTTP apis. While the
* default behavior is to raise exeception, users can alternatively enable 404 processing via
* {@link feign.Feign.Builder#decode404()}.
* {@link feign.Feign.Builder#dismiss404()}.
*/
public interface ErrorDecoder {

2
core/src/main/java/feign/codec/StringDecoder.java

@ -24,7 +24,7 @@ public class StringDecoder implements Decoder { @@ -24,7 +24,7 @@ public class StringDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
Response.Body body = response.body();
if (body == null) {
if (response.status() == 404 || response.status() == 204 || body == null) {
return null;
}
if (String.class.equals(type)) {

12
core/src/test/java/feign/AsyncFeignTest.java

@ -583,12 +583,12 @@ public class AsyncFeignTest { @@ -583,12 +583,12 @@ public class AsyncFeignTest {
}
@Test
public void decodingExceptionGetWrappedInDecode404Mode() throws Throwable {
public void decodingExceptionGetWrappedInDismiss404Mode() throws Throwable {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(DecodeException.class);
thrown.expectCause(isA(NoSuchElementException.class));;
TestInterfaceAsync api = new TestInterfaceAsyncBuilder().decode404().decoder(new Decoder() {
TestInterfaceAsync api = new TestInterfaceAsyncBuilder().dismiss404().decoder(new Decoder() {
@Override
public Object decode(Response response, Type type) throws IOException {
assertEquals(404, response.status());
@ -600,11 +600,11 @@ public class AsyncFeignTest { @@ -600,11 +600,11 @@ public class AsyncFeignTest {
}
@Test
public void decodingDoesNotSwallow404ErrorsInDecode404Mode() throws Throwable {
public void decodingDoesNotSwallow404ErrorsInDismiss404Mode() throws Throwable {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(IllegalArgumentException.class);
TestInterfaceAsync api = new TestInterfaceAsyncBuilder().decode404()
TestInterfaceAsync api = new TestInterfaceAsyncBuilder().dismiss404()
.errorDecoder(new IllegalArgumentExceptionOn404())
.target("http://localhost:" + server.getPort());
@ -1010,8 +1010,8 @@ public class AsyncFeignTest { @@ -1010,8 +1010,8 @@ public class AsyncFeignTest {
return this;
}
TestInterfaceAsyncBuilder decode404() {
delegate.decode404();
TestInterfaceAsyncBuilder dismiss404() {
delegate.dismiss404();
return this;
}

4
core/src/test/java/feign/FeignBuilderTest.java

@ -66,7 +66,7 @@ public class FeignBuilderTest { @@ -66,7 +66,7 @@ public class FeignBuilderTest {
/** Shows exception handling isn't required to coerce 404 to null or empty */
@Test
public void testDecode404() {
public void testDismiss404() {
server.enqueue(new MockResponse().setResponseCode(404));
server.enqueue(new MockResponse().setResponseCode(404));
server.enqueue(new MockResponse().setResponseCode(404));
@ -75,7 +75,7 @@ public class FeignBuilderTest { @@ -75,7 +75,7 @@ public class FeignBuilderTest {
server.enqueue(new MockResponse().setResponseCode(400));
String url = "http://localhost:" + server.getPort();
TestInterface api = Feign.builder().decode404().target(TestInterface.class, url);
TestInterface api = Feign.builder().dismiss404().target(TestInterface.class, url);
assertThat(api.getQueues("/")).isEmpty(); // empty, not null!
assertThat(api.decodedLazyPost().hasNext()).isFalse(); // empty, not null!

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

@ -723,13 +723,13 @@ public class FeignTest { @@ -723,13 +723,13 @@ public class FeignTest {
}
@Test
public void decodingExceptionGetWrappedInDecode404Mode() throws Exception {
public void decodingExceptionGetWrappedInDismiss404Mode() throws Exception {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(DecodeException.class);
thrown.expectCause(isA(NoSuchElementException.class));;
TestInterface api = new TestInterfaceBuilder()
.decode404()
.dismiss404()
.decoder(new Decoder() {
@Override
public Object decode(Response response, Type type) throws IOException {
@ -741,12 +741,12 @@ public class FeignTest { @@ -741,12 +741,12 @@ public class FeignTest {
}
@Test
public void decodingDoesNotSwallow404ErrorsInDecode404Mode() throws Exception {
public void decodingDoesNotSwallow404ErrorsInDismiss404Mode() throws Exception {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(IllegalArgumentException.class);
TestInterface api = new TestInterfaceBuilder()
.decode404()
.dismiss404()
.errorDecoder(new IllegalArgumentExceptionOn404())
.target("http://localhost:" + server.getPort());
api.queryMap(Collections.<String, Object>emptyMap());
@ -1168,8 +1168,8 @@ public class FeignTest { @@ -1168,8 +1168,8 @@ public class FeignTest {
return this;
}
TestInterfaceBuilder decode404() {
delegate.decode404();
TestInterfaceBuilder dismiss404() {
delegate.dismiss404();
return this;
}

12
core/src/test/java/feign/FeignUnderAsyncTest.java

@ -582,13 +582,13 @@ public class FeignUnderAsyncTest { @@ -582,13 +582,13 @@ public class FeignUnderAsyncTest {
}
@Test
public void decodingExceptionGetWrappedInDecode404Mode() throws Exception {
public void decodingExceptionGetWrappedInDismiss404Mode() throws Exception {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(DecodeException.class);
thrown.expectCause(isA(NoSuchElementException.class));;
TestInterface api = new TestInterfaceBuilder()
.decode404()
.dismiss404()
.decoder(new Decoder() {
@Override
public Object decode(Response response, Type type) throws IOException {
@ -600,12 +600,12 @@ public class FeignUnderAsyncTest { @@ -600,12 +600,12 @@ public class FeignUnderAsyncTest {
}
@Test
public void decodingDoesNotSwallow404ErrorsInDecode404Mode() throws Exception {
public void decodingDoesNotSwallow404ErrorsInDismiss404Mode() throws Exception {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(IllegalArgumentException.class);
TestInterface api = new TestInterfaceBuilder()
.decode404()
.dismiss404()
.errorDecoder(new IllegalArgumentExceptionOn404())
.target("http://localhost:" + server.getPort());
api.queryMap(Collections.<String, Object>emptyMap());
@ -1000,8 +1000,8 @@ public class FeignUnderAsyncTest { @@ -1000,8 +1000,8 @@ public class FeignUnderAsyncTest {
return this;
}
TestInterfaceBuilder decode404() {
delegate.decode404();
TestInterfaceBuilder dismiss404() {
delegate.dismiss404();
return this;
}

2
core/src/test/java/feign/optionals/OptionalDecoderTests.java

@ -40,7 +40,7 @@ public class OptionalDecoderTests { @@ -40,7 +40,7 @@ public class OptionalDecoderTests {
server.enqueue(new MockResponse().setBody("foo"));
final OptionalInterface api = Feign.builder()
.decode404()
.dismiss404()
.decoder(new OptionalDecoder(new Decoder.Default()))
.target(OptionalInterface.class, server.url("/").toString());

3
gson/src/main/java/feign/gson/GsonDecoder.java

@ -21,6 +21,7 @@ import java.io.Reader; @@ -21,6 +21,7 @@ import java.io.Reader;
import java.lang.reflect.Type;
import java.util.Collections;
import feign.Response;
import feign.Util;
import feign.codec.Decoder;
import static feign.Util.UTF_8;
import static feign.Util.ensureClosed;
@ -43,6 +44,8 @@ public class GsonDecoder implements Decoder { @@ -43,6 +44,8 @@ public class GsonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
Reader reader = response.body().asReader(UTF_8);

6
gson/src/test/java/feign/gson/GsonCodecTest.java

@ -226,15 +226,15 @@ public class GsonCodecTest { @@ -226,15 +226,15 @@ public class GsonCodecTest {
+ "]");
}
/** Enabled via {@link feign.Feign.Builder#decode404()} */
/** Enabled via {@link feign.Feign.Builder#dismiss404()} */
@Test
public void notFoundDecodesToNull() throws Exception {
public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
.headers(Collections.emptyMap())
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.build();
assertThat((byte[]) new GsonDecoder().decode(response, byte[].class)).isNull();
assertThat((byte[]) new GsonDecoder().decode(response, byte[].class)).isEmpty();
}
}

12
hc5/src/test/java/feign/hc5/AsyncApacheHttp5ClientTest.java

@ -529,13 +529,13 @@ public class AsyncApacheHttp5ClientTest { @@ -529,13 +529,13 @@ public class AsyncApacheHttp5ClientTest {
}
@Test
public void decodingExceptionGetWrappedInDecode404Mode() throws Throwable {
public void decodingExceptionGetWrappedInDismiss404Mode() throws Throwable {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(DecodeException.class);
thrown.expectCause(isA(NoSuchElementException.class));
final TestInterfaceAsync api =
new TestInterfaceAsyncBuilder().decode404().decoder((response, type) -> {
new TestInterfaceAsyncBuilder().dismiss404().decoder((response, type) -> {
assertEquals(404, response.status());
throw new NoSuchElementException();
}).target("http://localhost:" + server.getPort());
@ -544,11 +544,11 @@ public class AsyncApacheHttp5ClientTest { @@ -544,11 +544,11 @@ public class AsyncApacheHttp5ClientTest {
}
@Test
public void decodingDoesNotSwallow404ErrorsInDecode404Mode() throws Throwable {
public void decodingDoesNotSwallow404ErrorsInDismiss404Mode() throws Throwable {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(IllegalArgumentException.class);
final TestInterfaceAsync api = new TestInterfaceAsyncBuilder().decode404()
final TestInterfaceAsync api = new TestInterfaceAsyncBuilder().dismiss404()
.errorDecoder(new IllegalArgumentExceptionOn404())
.target("http://localhost:" + server.getPort());
@ -956,8 +956,8 @@ public class AsyncApacheHttp5ClientTest { @@ -956,8 +956,8 @@ public class AsyncApacheHttp5ClientTest {
return this;
}
TestInterfaceAsyncBuilder decode404() {
delegate.decode404();
TestInterfaceAsyncBuilder dismiss404() {
delegate.dismiss404();
return this;
}

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

@ -192,6 +192,11 @@ public final class HystrixFeign { @@ -192,6 +192,11 @@ public final class HystrixFeign {
return (Builder) super.decode404();
}
@Override
public Builder dismiss404() {
return (Builder) super.dismiss404();
}
@Override
public Builder errorDecoder(ErrorDecoder errorDecoder) {
return (Builder) super.errorDecoder(errorDecoder);

3
jackson-jaxb/src/main/java/feign/jackson/jaxb/JacksonJaxbJsonDecoder.java

@ -19,6 +19,7 @@ import java.io.IOException; @@ -19,6 +19,7 @@ import java.io.IOException;
import java.lang.reflect.Type;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.Decoder;
import static com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
@ -36,6 +37,8 @@ public final class JacksonJaxbJsonDecoder implements Decoder { @@ -36,6 +37,8 @@ public final class JacksonJaxbJsonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
return jacksonJaxbJsonProvider.readFrom(Object.class, type, null, APPLICATION_JSON_TYPE, null,

6
jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java

@ -56,17 +56,17 @@ public class JacksonJaxbCodecTest { @@ -56,17 +56,17 @@ public class JacksonJaxbCodecTest {
}
/**
* Enabled via {@link feign.Feign.Builder#decode404()}
* Enabled via {@link feign.Feign.Builder#dismiss404()}
*/
@Test
public void notFoundDecodesToNull() throws Exception {
public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertThat((byte[]) new JacksonJaxbJsonDecoder().decode(response, byte[].class)).isNull();
assertThat((byte[]) new JacksonJaxbJsonDecoder().decode(response, byte[].class)).isEmpty();
}
@XmlRootElement

4
jackson-jr/src/main/java/feign/jackson/jr/JacksonJrDecoder.java

@ -17,6 +17,7 @@ import com.fasterxml.jackson.jr.ob.JSON; @@ -17,6 +17,7 @@ import com.fasterxml.jackson.jr.ob.JSON;
import com.fasterxml.jackson.jr.ob.JSONObjectException;
import com.fasterxml.jackson.jr.ob.JacksonJrExtension;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import java.io.BufferedReader;
@ -65,6 +66,9 @@ public class JacksonJrDecoder extends JacksonJrMapper implements Decoder { @@ -65,6 +66,9 @@ public class JacksonJrDecoder extends JacksonJrMapper implements Decoder {
Transformer transformer = findTransformer(response, type);
if (response.status() == 404 || response.status() == 204) {
return Util.emptyValueOf(type);
}
if (response.body() == null) {
return null;
}

14
jackson-jr/src/test/java/feign/jackson/jr/JacksonCodecTest.java

@ -90,18 +90,18 @@ public class JacksonCodecTest { @@ -90,18 +90,18 @@ public class JacksonCodecTest {
}
@Test
public void nullBodyDecodesToNull() throws Exception {
public void nullBodyDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertNull(new JacksonJrDecoder().decode(response, String.class));
assertThat((byte[]) new JacksonJrDecoder().decode(response, byte[].class)).isEmpty();
}
@Test
public void emptyBodyDecodesToNull() throws Exception {
public void emptyBodyDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
@ -109,7 +109,7 @@ public class JacksonCodecTest { @@ -109,7 +109,7 @@ public class JacksonCodecTest {
.headers(Collections.emptyMap())
.body(new byte[0])
.build();
assertNull(new JacksonJrDecoder().decode(response, String.class));
assertThat((byte[]) new JacksonJrDecoder().decode(response, byte[].class)).isEmpty();
}
@Test
@ -271,15 +271,15 @@ public class JacksonCodecTest { @@ -271,15 +271,15 @@ public class JacksonCodecTest {
}
}
/** Enabled via {@link feign.Feign.Builder#decode404()} */
/** Enabled via {@link feign.Feign.Builder#dismiss404()} */
@Test
public void notFoundDecodesToNull() throws Exception {
public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertThat((byte[]) new JacksonJrDecoder().decode(response, byte[].class)).isNull();
assertThat((byte[]) new JacksonJrDecoder().decode(response, byte[].class)).isEmpty();
}
}

2
jackson/src/main/java/feign/jackson/JacksonDecoder.java

@ -47,6 +47,8 @@ public class JacksonDecoder implements Decoder { @@ -47,6 +47,8 @@ public class JacksonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
Reader reader = response.body().asReader(response.charset());

3
jackson/src/main/java/feign/jackson/JacksonIteratorDecoder.java

@ -18,6 +18,7 @@ import com.fasterxml.jackson.core.JsonToken; @@ -18,6 +18,7 @@ import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.*;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import java.io.BufferedReader;
@ -64,6 +65,8 @@ public final class JacksonIteratorDecoder implements Decoder { @@ -64,6 +65,8 @@ public final class JacksonIteratorDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
Reader reader = response.body().asReader(UTF_8);

20
jackson/src/test/java/feign/jackson/JacksonCodecTest.java

@ -223,18 +223,18 @@ public class JacksonCodecTest { @@ -223,18 +223,18 @@ public class JacksonCodecTest {
}
@Test
public void nullBodyDecodesToNullIterator() throws Exception {
public void nullBodyDecodesToEmptyIterator() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertNull(JacksonIteratorDecoder.create().decode(response, Iterator.class));
assertThat((byte[]) JacksonIteratorDecoder.create().decode(response, byte[].class)).isEmpty();
}
@Test
public void emptyBodyDecodesToNullIterator() throws Exception {
public void emptyBodyDecodesToEmptyIterator() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
@ -242,7 +242,7 @@ public class JacksonCodecTest { @@ -242,7 +242,7 @@ public class JacksonCodecTest {
.headers(Collections.emptyMap())
.body(new byte[0])
.build();
assertNull(JacksonIteratorDecoder.create().decode(response, Iterator.class));
assertThat((byte[]) JacksonIteratorDecoder.create().decode(response, byte[].class)).isEmpty();
}
static class Zone extends LinkedHashMap<String, Object> {
@ -304,27 +304,27 @@ public class JacksonCodecTest { @@ -304,27 +304,27 @@ public class JacksonCodecTest {
}
}
/** Enabled via {@link feign.Feign.Builder#decode404()} */
/** Enabled via {@link feign.Feign.Builder#dismiss404()} */
@Test
public void notFoundDecodesToNull() throws Exception {
public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertThat((byte[]) new JacksonDecoder().decode(response, byte[].class)).isNull();
assertThat((byte[]) new JacksonDecoder().decode(response, byte[].class)).isEmpty();
}
/** Enabled via {@link feign.Feign.Builder#decode404()} */
/** Enabled via {@link feign.Feign.Builder#dismiss404()} */
@Test
public void notFoundDecodesToNullIterator() throws Exception {
public void notFoundDecodesToEmptyIterator() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertThat((byte[]) JacksonIteratorDecoder.create().decode(response, byte[].class)).isNull();
assertThat((byte[]) JacksonIteratorDecoder.create().decode(response, byte[].class)).isEmpty();
}
}

12
java11/src/test/java/feign/http2client/test/Http2ClientAsyncTest.java

@ -577,13 +577,13 @@ public class Http2ClientAsyncTest { @@ -577,13 +577,13 @@ public class Http2ClientAsyncTest {
}
@Test
public void decodingExceptionGetWrappedInDecode404Mode() throws Throwable {
public void decodingExceptionGetWrappedInDismiss404Mode() throws Throwable {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(DecodeException.class);
thrown.expectCause(isA(NoSuchElementException.class));
final TestInterfaceAsync api =
newAsyncBuilder().decode404().decoder((response, type) -> {
newAsyncBuilder().dismiss404().decoder((response, type) -> {
assertEquals(404, response.status());
throw new NoSuchElementException();
}).target("http://localhost:" + server.getPort());
@ -592,11 +592,11 @@ public class Http2ClientAsyncTest { @@ -592,11 +592,11 @@ public class Http2ClientAsyncTest {
}
@Test
public void decodingDoesNotSwallow404ErrorsInDecode404Mode() throws Throwable {
public void decodingDoesNotSwallow404ErrorsInDismiss404Mode() throws Throwable {
server.enqueue(new MockResponse().setResponseCode(404));
thrown.expect(IllegalArgumentException.class);
final TestInterfaceAsync api = newAsyncBuilder().decode404()
final TestInterfaceAsync api = newAsyncBuilder().dismiss404()
.errorDecoder(new IllegalArgumentExceptionOn404())
.target("http://localhost:" + server.getPort());
@ -1010,8 +1010,8 @@ public class Http2ClientAsyncTest { @@ -1010,8 +1010,8 @@ public class Http2ClientAsyncTest {
return this;
}
TestInterfaceAsyncBuilder decode404() {
delegate.decode404();
TestInterfaceAsyncBuilder dismiss404() {
delegate.dismiss404();
return this;
}

2
jaxb/src/main/java/feign/jaxb/JAXBDecoder.java

@ -64,7 +64,7 @@ public class JAXBDecoder implements Decoder { @@ -64,7 +64,7 @@ public class JAXBDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
if (response.status() == 204)
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;

6
jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java

@ -242,10 +242,10 @@ public class JAXBCodecTest { @@ -242,10 +242,10 @@ public class JAXBCodecTest {
}
/**
* Enabled via {@link feign.Feign.Builder#decode404()}
* Enabled via {@link feign.Feign.Builder#dismiss404()}
*/
@Test
public void notFoundDecodesToNull() throws Exception {
public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
@ -253,7 +253,7 @@ public class JAXBCodecTest { @@ -253,7 +253,7 @@ public class JAXBCodecTest {
.headers(Collections.<String, Collection<String>>emptyMap())
.build();
assertThat((byte[]) new JAXBDecoder(new JAXBContextFactory.Builder().build())
.decode(response, byte[].class)).isNull();
.decode(response, byte[].class)).isEmpty();
}
@XmlRootElement

9
json/src/main/java/feign/json/JsonDecoder.java

@ -24,7 +24,6 @@ import java.io.BufferedReader; @@ -24,7 +24,6 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import static feign.Util.UTF_8;
import static java.lang.String.format;
/**
@ -53,6 +52,14 @@ public class JsonDecoder implements Decoder { @@ -53,6 +52,14 @@ public class JsonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException {
if (response.status() == 404 || response.status() == 204)
if (JSONObject.class.isAssignableFrom((Class<?>) type))
return new JSONObject();
else if (JSONArray.class.isAssignableFrom((Class<?>) type))
return new JSONArray();
else
throw new DecodeException(response.status(),
format("%s is not a type supported by this decoder.", type), response.request());
if (response.body() == null)
return null;
try (Reader reader = response.body().asReader(response.charset())) {

37
json/src/test/java/feign/json/JsonDecoderTest.java

@ -56,7 +56,7 @@ public class JsonDecoderTest { @@ -56,7 +56,7 @@ public class JsonDecoderTest {
public void decodesArray() throws IOException {
String json = "[{\"a\":\"b\",\"c\":1},123]";
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(json, UTF_8)
@ -69,7 +69,7 @@ public class JsonDecoderTest { @@ -69,7 +69,7 @@ public class JsonDecoderTest {
public void decodesObject() throws IOException {
String json = "{\"a\":\"b\",\"c\":1}";
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(json, UTF_8)
@ -79,18 +79,29 @@ public class JsonDecoderTest { @@ -79,18 +79,29 @@ public class JsonDecoderTest {
}
@Test
public void nullBodyDecodesToNull() throws IOException {
public void notFoundDecodesToEmpty() throws IOException {
Response response = Response.builder()
.status(404)
.reason("Not found")
.headers(Collections.emptyMap())
.request(request)
.build();
assertTrue(((JSONObject) new JsonDecoder().decode(response, JSONObject.class)).isEmpty());
}
@Test
public void nullBodyDecodesToEmpty() throws IOException {
Response response = Response.builder()
.status(204)
.reason("OK")
.headers(Collections.emptyMap())
.request(request)
.build();
assertNull(new JsonDecoder().decode(response, JSONObject.class));
assertTrue(((JSONObject) new JsonDecoder().decode(response, JSONObject.class)).isEmpty());
}
@Test
public void emptyBodyDecodesToNull() throws IOException {
public void emptyBodyDecodesToEmpty() throws IOException {
Response response = Response.builder()
.status(204)
.reason("OK")
@ -98,14 +109,14 @@ public class JsonDecoderTest { @@ -98,14 +109,14 @@ public class JsonDecoderTest {
.body("", UTF_8)
.request(request)
.build();
assertNull(new JsonDecoder().decode(response, JSONObject.class));
assertTrue(((JSONObject) new JsonDecoder().decode(response, JSONObject.class)).isEmpty());
}
@Test
public void unknownTypeThrowsDecodeException() throws IOException {
String json = "[{\"a\":\"b\",\"c\":1},123]";
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(json, UTF_8)
@ -121,7 +132,7 @@ public class JsonDecoderTest { @@ -121,7 +132,7 @@ public class JsonDecoderTest {
public void badJsonThrowsWrappedJSONException() throws IOException {
String json = "{\"a\":\"b\",\"c\":1}";
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(json, UTF_8)
@ -140,7 +151,7 @@ public class JsonDecoderTest { @@ -140,7 +151,7 @@ public class JsonDecoderTest {
when(body.asReader(any())).thenThrow(new JSONException("test exception",
new Exception("test cause exception")));
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(body)
@ -157,7 +168,7 @@ public class JsonDecoderTest { @@ -157,7 +168,7 @@ public class JsonDecoderTest {
when(body.asReader(any())).thenThrow(new JSONException("test exception",
new IOException("test cause exception")));
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(body)
@ -173,7 +184,7 @@ public class JsonDecoderTest { @@ -173,7 +184,7 @@ public class JsonDecoderTest {
Response.Body body = mock(Response.Body.class);
when(body.asReader(any())).thenThrow(new IOException("test exception"));
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(body)
@ -188,7 +199,7 @@ public class JsonDecoderTest { @@ -188,7 +199,7 @@ public class JsonDecoderTest {
public void decodesExtendedArray() throws IOException {
String json = "[{\"a\":\"b\",\"c\":1},123]";
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(json, UTF_8)
@ -201,7 +212,7 @@ public class JsonDecoderTest { @@ -201,7 +212,7 @@ public class JsonDecoderTest {
public void decodeExtendedObject() throws IOException {
String json = "{\"a\":\"b\",\"c\":1}";
Response response = Response.builder()
.status(204)
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
.body(json, UTF_8)

2
reactive/src/test/java/feign/reactive/ReactiveFeignIntegrationTest.java

@ -90,7 +90,7 @@ public class ReactiveFeignIntegrationTest { @@ -90,7 +90,7 @@ public class ReactiveFeignIntegrationTest {
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.logger(new ConsoleLogger())
.decode404()
.dismiss404()
.options(new Options())
.logLevel(Level.FULL)
.target(TestReactorService.class, this.getServerUrl());

3
sax/src/main/java/feign/sax/SAXDecoder.java

@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
*/
package feign.sax;
import feign.Util;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@ -60,6 +61,8 @@ public class SAXDecoder implements Decoder { @@ -60,6 +61,8 @@ public class SAXDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException {
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
ContentHandlerWithResult.Factory<?> handlerFactory = handlerFactories.get(type);

10
sax/src/test/java/feign/sax/SAXDecoderTest.java

@ -83,26 +83,26 @@ public class SAXDecoderTest { @@ -83,26 +83,26 @@ public class SAXDecoderTest {
}
@Test
public void nullBodyDecodesToNull() throws Exception {
public void nullBodyDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.<String, Collection<String>>emptyMap())
.build();
assertNull(decoder.decode(response, String.class));
assertThat((byte[]) decoder.decode(response, byte[].class)).isEmpty();
}
/** Enabled via {@link feign.Feign.Builder#decode404()} */
/** Enabled via {@link feign.Feign.Builder#dismiss404()} */
@Test
public void notFoundDecodesToNull() throws Exception {
public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.<String, Collection<String>>emptyMap())
.build();
assertThat((byte[]) decoder.decode(response, byte[].class)).isNull();
assertThat((byte[]) decoder.decode(response, byte[].class)).isEmpty();
}
static enum NetworkStatus {

7
soap/src/test/java/feign/soap/SOAPCodecTest.java

@ -37,7 +37,6 @@ import feign.Response; @@ -37,7 +37,6 @@ import feign.Response;
import feign.Util;
import feign.codec.Encoder;
import feign.jaxb.JAXBContextFactory;
import feign.jaxb.JAXBDecoder;
@SuppressWarnings("deprecation")
public class SOAPCodecTest {
@ -370,7 +369,7 @@ public class SOAPCodecTest { @@ -370,7 +369,7 @@ public class SOAPCodecTest {
}
/**
* Enabled via {@link feign.Feign.Builder#decode404()}
* Enabled via {@link feign.Feign.Builder#dismiss404()}
*/
@Test
public void notFoundDecodesToNull() throws Exception {
@ -380,8 +379,8 @@ public class SOAPCodecTest { @@ -380,8 +379,8 @@ public class SOAPCodecTest {
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.build();
assertThat((byte[]) new JAXBDecoder(new JAXBContextFactory.Builder().build())
.decode(response, byte[].class)).isNull();
assertThat((byte[]) new SOAPDecoder(new JAXBContextFactory.Builder().build())
.decode(response, byte[].class)).isEmpty();
}

Loading…
Cancel
Save