Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

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 authored
    Improves RetrofitError.toString() output.
Commits on Mar 25, 2013
  1. @sberan

    Add hook for customizing exceptions

    sberan authored
    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.
This page is out of date. Refresh to see the latest.
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.