Skip to content

Commit

Permalink
issue #9: fallback handling is differs between sync and observer resp…
Browse files Browse the repository at this point in the history
…onses; decouple from error handling
  • Loading branch information
adriancole committed Jul 7, 2013
1 parent 232c974 commit 46be6bd
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### Version 3.0
* decoupled ErrorDecoder from fallback handling

### Version 2.0.0
* removes guava and jax-rs dependencies
* adds JAX-RS integration
Expand Down
2 changes: 1 addition & 1 deletion feign-core/src/main/java/feign/MethodHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public Object executeAndDecode(String configKey, RequestTemplate template, Type
}
return decoder.decode(configKey, response, returnType);
} else {
return errorDecoder.decode(configKey, response, returnType);
throw errorDecoder.decode(configKey, response);
}
} catch (Throwable e) {
ensureBodyClosed(response);
Expand Down
37 changes: 15 additions & 22 deletions feign-core/src/main/java/feign/codec/ErrorDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package feign.codec;

import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
Expand All @@ -35,22 +34,20 @@
import static java.util.concurrent.TimeUnit.SECONDS;

/**
* Allows you to massage an exception into a application-specific one, or
* fallback to a default value. Falling back to null on
* {@link Response#status() status 404}, or converting out to a throttle
* Allows you to massage an exception into a application-specific one. Converting out to a throttle
* exception are examples of this in use.
* <br>
* Ex.
* <br>
* <pre>
* class IllegalArgumentExceptionOn404Decoder extends ErrorDecoder {
*
* &#064;Override
* public Object decode(String methodKey, Response response, Type&lt;?&gt; type) throws Throwable {
* &#064;Override
* public Exception decode(String methodKey, Response response) {
* if (response.status() == 404)
* throw new IllegalArgumentException(&quot;zone not found&quot;);
* return ErrorDecoder.DEFAULT.decode(request, response, type);
* }
* return ErrorDecoder.DEFAULT.decode(methodKey, request, response);
* }
*
* }
* </pre>
Expand All @@ -59,33 +56,29 @@ public interface ErrorDecoder {

/**
* Implement this method in order to decode an HTTP {@link Response} when
* {@link Response#status()} is not in the 2xx range. Please raise
* application-specific exceptions or return fallback values where possible.
* If your exception is retryable, wrap or subclass
* {@link RetryableException}
* {@link Response#status()} is not in the 2xx range. Please raise application-specific exceptions where possible.
* If your exception is retryable, wrap or subclass {@link RetryableException}
*
* @param methodKey {@link feign.Feign#configKey} of the java method that invoked the request. ex. {@code IAM#getUser()}
* @param response HTTP response where {@link Response#status() status} is greater than or equal to {@code 300}.
* @param type Target object type.
* @return instance of {@code type}
* @throws Throwable IOException, if there was a network error reading the
* response or an application-specific exception decoded by the
* implementation. If the throwable is retryable, it should be
* wrapped, or a subtype of {@link RetryableException}
* @return Exception IOException, if there was a network error reading the
* response or an application-specific exception decoded by the
* implementation. If the throwable is retryable, it should be
* wrapped, or a subtype of {@link RetryableException}
*/
public Object decode(String methodKey, Response response, Type type) throws Throwable;
public Exception decode(String methodKey, Response response);

public static final ErrorDecoder DEFAULT = new ErrorDecoder() {

private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();

@Override
public Object decode(String methodKey, Response response, Type type) throws Throwable {
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null)
throw new RetryableException(exception.getMessage(), exception, retryAfter);
throw exception;
return new RetryableException(exception.getMessage(), exception, retryAfter);
return exception;
}

private <T> T firstOrNull(Map<String, Collection<T>> map, String key) {
Expand Down
6 changes: 3 additions & 3 deletions feign-core/src/test/java/feign/FeignTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ public void canOverrideErrorDecoderOnMethod() throws IOException, InterruptedExc
return ImmutableMap.<String, ErrorDecoder>of("TestInterface#post()", new ErrorDecoder() {

@Override
public Object decode(String methodKey, Response response, Type type) throws Throwable {
public Exception decode(String methodKey, Response response) {
if (response.status() == 404)
throw new IllegalArgumentException("zone not found");
return ErrorDecoder.DEFAULT.decode(methodKey, response, type);
return new IllegalArgumentException("zone not found");
return ErrorDecoder.DEFAULT.decode(methodKey, response);
}

});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ public void throwsFeignException() throws Throwable {
Response response = Response.create(500, "Internal server error", ImmutableMap.<String, Collection<String>>of(),
null);

ErrorDecoder.DEFAULT.decode("Service#foo()", response, void.class);
throw ErrorDecoder.DEFAULT.decode("Service#foo()", response);
}

@Test(expectedExceptions = FeignException.class, expectedExceptionsMessageRegExp = "status 500 reading Service#foo\\(\\); content:\nhello world")
public void throwsFeignExceptionIncludingBody() throws Throwable {
Response response = Response.create(500, "Internal server error", ImmutableMap.<String, Collection<String>>of(),
"hello world");

ErrorDecoder.DEFAULT.decode("Service#foo()", response, void.class);
throw ErrorDecoder.DEFAULT.decode("Service#foo()", response);
}

@Test(expectedExceptions = RetryableException.class, expectedExceptionsMessageRegExp = "status 503 reading Service#foo\\(\\)")
public void retryAfterHeaderThrowsRetryableException() throws Throwable {
Response response = Response.create(503, "Service Unavailable",
ImmutableMultimap.of(RETRY_AFTER, "Sat, 1 Jan 2000 00:00:00 GMT").asMap(), null);

ErrorDecoder.DEFAULT.decode("Service#foo()", response, void.class);
throw ErrorDecoder.DEFAULT.decode("Service#foo()", response);
}
}

0 comments on commit 46be6bd

Please sign in to comment.