Skip to content

Loading…

Error Handling #1

Merged
merged 2 commits into from

3 participants

@sberan

Add hook to allow clients to customize RetrofitError exceptions.

Instead of having to catch RetrofitError for things like invalid requests (say, in the case of an invalid business object), a client can provide an ErrorHandler, which can customize the retrofit error, throwing a custom error exception on a case-by-case basis.

The ErrorHandler must also handle the async case, where it can call a custom callback method based on the original RetrofitError.

codebutler and others added some commits
@codebutler codebutler Use standard exception cause handling in RetrofitError.
Improves RetrofitError.toString() output.
b0f6de6
@sberan sberan Add hook for customizing exceptions
The exceptions can be customized via a new ErrorHandler class, which is
responsible for throwing customized exceptions during synchronous
requests, or calling an appropriate callback method for asynchronous
requests.
dbe11dd
@ransombriggs ransombriggs merged commit 9505178 into ransombriggs:uiowaChanges
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 19, 2013
  1. @codebutler

    Use standard exception cause handling in RetrofitError.

    codebutler committed
    Improves RetrofitError.toString() output.
Commits on Mar 25, 2013
  1. @sberan

    Add hook for customizing exceptions

    sberan committed
    The exceptions can be customized via a new ErrorHandler class, which is
    responsible for throwing customized exceptions during synchronous
    requests, or calling an appropriate callback method for asynchronous
    requests.
View
6 retrofit/src/main/java/retrofit/http/CallbackRunnable.java
@@ -12,10 +12,12 @@
abstract class CallbackRunnable<T> implements Runnable {
private final Callback<T> callback;
private final Executor callbackExecutor;
+ private final ErrorHandler errorHandler;
- CallbackRunnable(Callback<T> callback, Executor callbackExecutor) {
+ CallbackRunnable(Callback<T> callback, Executor callbackExecutor, ErrorHandler errorHandler) {
this.callback = callback;
this.callbackExecutor = callbackExecutor;
+ this.errorHandler = errorHandler;
}
@SuppressWarnings("unchecked")
@@ -30,7 +32,7 @@
} catch (final RetrofitError e) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
- callback.failure(e);
+ errorHandler.handleErrorCallback(e, callback);
}
});
}
View
42 retrofit/src/main/java/retrofit/http/ErrorHandler.java
@@ -0,0 +1,42 @@
+package retrofit.http;
+
+/**
+ * A hook allowing clients to customize error exceptions and callbacks for requests.
+ *
+ *
+ * @author Sam Beran sberan@gmail.com
+ */
+public interface ErrorHandler {
+ /**
+ * Called when errors occur during synchronous requests. The returned
+ * exception will be thrown from the client's interface method.
+ *
+ * If the exception is checked, the exception must be declared to be
+ * thrown on the interface method.
+ *
+ * @param e - the original RetrofitError exception
+ * @return an exception which will be thrown from the client interface method.
+ */
+ Throwable handleError(RetrofitError e);
+
+ /**
+ * Called when errors occur during asynchronous requests. This method is responsible
+ * for calling the appropriate failure method in the callback class.
+ *
+ * @param e the original RetrofitError exception
+ * @param callback the callback which was passed to the interface method
+ */
+ void handleErrorCallback(RetrofitError e, Callback<?> callback);
+
+ static ErrorHandler DEFAULT = new ErrorHandler() {
+ @Override
+ public Throwable handleError(RetrofitError e) {
+ return e;
+ }
+
+ @Override
+ public void handleErrorCallback(RetrofitError e, Callback<?> callback) {
+ callback.failure(e);
+ }
+ };
+}
View
26 retrofit/src/main/java/retrofit/http/RestAdapter.java
@@ -41,9 +41,10 @@
private final Headers headers;
private final Converter converter;
private final Profiler profiler;
+ private final ErrorHandler errorHandler;
private RestAdapter(Server server, Client.Provider clientProvider, Executor httpExecutor,
- Executor callbackExecutor, Headers headers, Converter converter, Profiler profiler) {
+ Executor callbackExecutor, Headers headers, Converter converter, Profiler profiler, ErrorHandler errorHandler) {
this.server = server;
this.clientProvider = clientProvider;
this.httpExecutor = httpExecutor;
@@ -51,6 +52,7 @@ private RestAdapter(Server server, Client.Provider clientProvider, Executor http
this.headers = headers;
this.converter = converter;
this.profiler = profiler;
+ this.errorHandler = errorHandler;
}
/**
@@ -99,7 +101,7 @@ private RestAdapter(Server server, Client.Provider clientProvider, Executor http
@SuppressWarnings("unchecked") //
@Override public Object invoke(Object proxy, Method method, final Object[] args)
- throws InvocationTargetException, IllegalAccessException {
+ throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
@@ -117,14 +119,18 @@ private RestAdapter(Server server, Client.Provider clientProvider, Executor http
}
if (methodDetails.isSynchronous) {
- return invokeRequest(methodDetails, args);
+ try {
+ return invokeRequest(methodDetails, args);
+ } catch (RetrofitError error) {
+ throw errorHandler.handleError(error);
+ }
}
if (httpExecutor == null || callbackExecutor == null) {
throw new IllegalStateException("Asynchronous invocation requires calling setExecutors.");
}
Callback<?> callback = (Callback<?>) args[args.length - 1];
- httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor) {
+ httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor, errorHandler) {
@Override public ResponseWrapper obtainResponse() {
return (ResponseWrapper) invokeRequest(methodDetails, args);
}
@@ -279,6 +285,7 @@ private static TypedInput logResponse(String url, int statusCode, TypedInput bod
private Headers headers;
private Converter converter;
private Profiler profiler;
+ private ErrorHandler errorHandler;
public Builder setServer(String endpoint) {
if (endpoint == null) throw new NullPointerException("endpoint");
@@ -340,13 +347,19 @@ public Builder setProfiler(Profiler profiler) {
return this;
}
+ public Builder setErrorHandler(ErrorHandler errorHandler) {
+ if(errorHandler == null) throw new RuntimeException("error handler cannot be null");
+ this.errorHandler = errorHandler;
+ return this;
+ }
+
public RestAdapter build() {
if (server == null) {
throw new IllegalArgumentException("Server may not be null.");
}
ensureSaneDefaults();
return new RestAdapter(server, clientProvider, httpExecutor, callbackExecutor,
- headers, converter, profiler);
+ headers, converter, profiler, errorHandler);
}
private void ensureSaneDefaults() {
@@ -365,6 +378,9 @@ private void ensureSaneDefaults() {
if (headers == null) {
headers = Headers.NONE;
}
+ if(errorHandler == null) {
+ errorHandler = ErrorHandler.DEFAULT;
+ }
}
}
}
View
10 retrofit/src/main/java/retrofit/http/RetrofitError.java
@@ -30,16 +30,15 @@ static RetrofitError unexpectedError(String url, Throwable exception) {
private final Converter converter;
private final Type successType;
private final boolean networkError;
- private final Throwable exception;
private RetrofitError(String url, Response response, Converter converter, Type successType,
boolean networkError, Throwable exception) {
+ super(exception);
this.url = url;
this.response = response;
this.converter = converter;
this.successType = successType;
this.networkError = networkError;
- this.exception = exception;
}
/** The request URL which produced the error. */
@@ -85,9 +84,4 @@ public Object getBodyAs(Type type) {
throw new RuntimeException(e);
}
}
-
- /** The exception which caused this error, if any. */
- public Throwable getException() {
- return exception;
- }
-}
+}
View
2 retrofit/src/test/java/retrofit/http/CallbackRunnableTest.java
@@ -20,7 +20,7 @@
@Before public void setUp() {
callback = mock(Callback.class);
- callbackRunnable = spy(new CallbackRunnable<Object>(callback, executor) {
+ callbackRunnable = spy(new CallbackRunnable<Object>(callback, executor, ErrorHandler.DEFAULT) {
@Override public ResponseWrapper obtainResponse() {
return null; // Must be mocked.
}
View
116 retrofit/src/test/java/retrofit/http/ErrorHandlerTest.java
@@ -0,0 +1,116 @@
+package retrofit.http;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import retrofit.http.client.Client;
+import retrofit.http.client.Request;
+import retrofit.http.client.Response;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+
+public class ErrorHandlerTest {
+
+
+ interface ExampleClient {
+ @POST("/") @Multipart
+ Void create(@Name("object") Object toCreate) throws InvalidObjectException;
+
+ @POST("/") @Multipart
+ void createAsync(@Name("object") Object toCreate, ValidatedCallback<Void> callback);
+ }
+
+
+ /* An HTTP client which always returns a 400 response */
+ static class MockInvalidResponseClient implements Client {
+ @Override
+ public Response execute(Request request) throws IOException {
+ return new Response(400, "invalid request", Collections.<Header>emptyList(), null);
+ }
+ }
+
+ /**
+ * An example exception representing an attempt to send an invalid object to the server
+ */
+ static class InvalidObjectException extends Exception {
+ public InvalidObjectException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * An example Callback for an asynchronous request with validation capability
+ */
+ interface ValidatedCallback<T> extends Callback<T> {
+ void invalid(InvalidObjectException cause);
+ }
+
+ /**
+ * A custom error handler, which is responsible for detecting a validation error and
+ * either returning an exception, or calling the appropriate callback method for async requests
+ */
+ static class InvalidObjectErrorHandler implements ErrorHandler {
+ private boolean isInvalidRequest(RetrofitError e) {
+ Response response = e.getResponse();
+ return response != null && response.getStatus() == 400 && "invalid request".equals(response.getReason());
+ }
+
+ @Override
+ public Throwable handleError(RetrofitError e) {
+ if(isInvalidRequest(e)) {
+ return new InvalidObjectException(e.getResponse().getReason());
+ }
+ return e;
+ }
+
+ @Override
+ public void handleErrorCallback(RetrofitError e, Callback<?> callback) {
+ if(callback instanceof ValidatedCallback && isInvalidRequest(e)) {
+ ((ValidatedCallback) callback).invalid(new InvalidObjectException(e.getResponse().getReason()));
+ } else {
+ callback.failure(e);
+ }
+ }
+ }
+
+ ExampleClient client;
+
+ @Before
+ public void setup() {
+ client = new RestAdapter.Builder()
+ .setServer("http://example.com")
+ .setClient(new MockInvalidResponseClient())
+ .setErrorHandler(new InvalidObjectErrorHandler())
+ .setExecutors(new Utils.SynchronousExecutor(), new Utils.SynchronousExecutor())
+ .build()
+ .create(ExampleClient.class);
+ }
+
+
+ @Test
+ public void testSynchronousExceptionThrown() {
+ try {
+ client.create("Example Value");
+ fail();
+ } catch (InvalidObjectException e) {
+ assertThat(e.getMessage()).isEqualTo("invalid request");
+ }
+ }
+
+ @Test
+ public void testAsyncInvalidCallback() {
+ @SuppressWarnings("unchecked")
+ ValidatedCallback<Void> mockCallback = Mockito.mock(ValidatedCallback.class);
+
+ client.createAsync("Example Value", mockCallback);
+
+ verify(mockCallback).invalid(any(InvalidObjectException.class));
+ }
+
+}
View
6 retrofit/src/test/java/retrofit/http/RestAdapterTest.java
@@ -110,7 +110,7 @@
fail("RetrofitError expected on malformed response body.");
} catch (RetrofitError e) {
assertThat(e.getResponse().getStatus()).isEqualTo(200);
- assertThat(e.getException()).isInstanceOf(ConversionException.class);
+ assertThat(e.getCause()).isInstanceOf(ConversionException.class);
assertThat(e.getResponse().getBody()).isEqualTo(new TypedString("{"));
}
}
@@ -135,7 +135,7 @@
example.something();
fail("RetrofitError expected when client throws exception.");
} catch (RetrofitError e) {
- assertThat(e.getException()).isSameAs(exception);
+ assertThat(e.getCause()).isSameAs(exception);
}
}
@@ -147,7 +147,7 @@
example.something();
fail("RetrofitError expected when unexpected exception thrown.");
} catch (RetrofitError e) {
- assertThat(e.getException()).isSameAs(exception);
+ assertThat(e.getCause()).isSameAs(exception);
}
}
Something went wrong with that request. Please try again.