Skip to content

Commit

Permalink
Introduce call, call adapters, and the response type.
Browse files Browse the repository at this point in the history
* `Call` is the mechanism for request execution. It encapsulates a single request action either synchronously or asynchronously, and can support canceling.
* `CallAdapter` is a means of supporting a method return type other than `Call` (like RxJava's `Observable`) by adapting the `Call` instance into another instance. This change moves RxJava support into a sibling module which uses an adapter for supporting `Observable` types.
* `Response` represents the HTTP response along side the deserialized body. It allows access to both the converted body and lower-level items such as the header, response code, and underlying OkHttp request and response objects.
  • Loading branch information
JakeWharton committed Jun 1, 2015
1 parent 87dd2c6 commit 2ca1950
Show file tree
Hide file tree
Showing 42 changed files with 2,078 additions and 1,983 deletions.
1 change: 0 additions & 1 deletion checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
<!-- See http://checkstyle.sf.net/config_coding.html -->
<!--module name="AvoidInlineConditionals"/-->
<module name="CovariantEquals"/>
<module name="DoubleCheckedLocking"/>
<module name="EmptyStatement"/>
<module name="EqualsAvoidNull"/>
<module name="EqualsHashCode"/>
Expand Down
11 changes: 6 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@

<modules>
<module>retrofit</module>
<module>retrofit-adapters</module>
<module>retrofit-converters</module>
<module>retrofit-mock</module>
<!--<module>retrofit-mock</module>-->
<module>samples</module>
</modules>

Expand All @@ -44,14 +45,14 @@
<project.reporting.sourceEncoding>UTF-8</project.reporting.sourceEncoding>

<!-- Compilation -->
<java.version>1.6</java.version>
<java.version>1.7</java.version>

<!-- Dependencies -->
<android.version>4.1.1.4</android.version>
<android.platform>16</android.platform>
<gson.version>2.3.1</gson.version>
<okhttp.version>2.4.0-RC1</okhttp.version>
<rxjava.version>1.0.0</rxjava.version>
<okhttp.version>2.4.0</okhttp.version>
<rxjava.version>1.0.10</rxjava.version>

<!-- Converter Dependencies -->
<protobuf.version>2.5.0</protobuf.version>
Expand Down Expand Up @@ -190,7 +191,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.9.1</version>
<version>2.15</version>
<configuration>
<failsOnError>true</failsOnError>
<configLocation>checkstyle.xml</configLocation>
Expand Down
4 changes: 4 additions & 0 deletions retrofit-adapters/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Retrofit Adapters
=================

TODO
20 changes: 20 additions & 0 deletions retrofit-adapters/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.squareup.retrofit</groupId>
<artifactId>parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>retrofit-adapters</artifactId>
<name>Adapters</name>
<packaging>pom</packaging>

<modules>
<module>rxjava</module>
</modules>
</project>
43 changes: 43 additions & 0 deletions retrofit-adapters/rxjava/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.squareup.retrofit</groupId>
<artifactId>retrofit-adapters</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>adapter-rxjava</artifactId>
<name>Adapter: RxJava</name>

<dependencies>
<dependency>
<groupId>com.squareup.retrofit</groupId>
<artifactId>retrofit</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Func1;
import rx.subscriptions.Subscriptions;

/**
* TODO docs
*/
public final class ObservableCallAdapterFactory implements CallAdapter.Factory {
/**
* TODO
*/
public static ObservableCallAdapterFactory create() {
return new ObservableCallAdapterFactory();
}

private ObservableCallAdapterFactory() {
}

@Override public String toString() {
return getClass().getSimpleName();
}

@Override public CallAdapter<?> get(Type returnType) {
if (Utils.getRawType(returnType) != Observable.class) {
return null;
}
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException("Observable return type must be parameterized"
+ " as Observable<Foo> or Observable<? extends Foo>");
}

Type observableType = Utils.getSingleParameterUpperBound((ParameterizedType) returnType);
Class<?> rawObservableType = Utils.getRawType(observableType);

if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Response must be parameterized"
+ " as Response<Foo> or Response<? extends Foo>");
}
Type responseType = Utils.getSingleParameterUpperBound((ParameterizedType) observableType);
return new ResponseCallAdapter<>(responseType);
}

if (rawObservableType == Result.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Result must be parameterized"
+ " as Result<Foo> or Result<? extends Foo>");
}
Type responseType = Utils.getSingleParameterUpperBound((ParameterizedType) observableType);
return new ResultCallAdapter<>(responseType);
}

return new SimpleCallAdapter(observableType);
}

static final class CallOnSubcribe<T> implements Observable.OnSubscribe<Response<T>> {
private final Call<T> originalCall;

private CallOnSubcribe(Call<T> originalCall) {
this.originalCall = originalCall;
}

@Override public void call(final Subscriber<? super Response<T>> subscriber) {
// Since Call is a one-shot type, clone it for each new subscriber.
final Call<T> call = originalCall.clone();

// Attempt to cancel the call if it is still in-flight on unsubscription.
subscriber.add(Subscriptions.create(new Action0() {
@Override public void call() {
call.cancel();
}
}));

call.enqueue(new Callback<T>() {
@Override public void success(Response<T> response) {
if (subscriber.isUnsubscribed()) {
return;
}
try {
subscriber.onNext(response);
} catch (Throwable t) {
subscriber.onError(t);
return;
}
subscriber.onCompleted();
}

@Override public void failure(Throwable t) {
if (subscriber.isUnsubscribed()) {
return;
}
subscriber.onError(t);
}
});
}
}

static final class ResponseCallAdapter<T> implements CallAdapter<T> {
private final Type responseType;

ResponseCallAdapter(Type responseType) {
this.responseType = responseType;
}

@Override public Type responseType() {
return responseType;
}

@Override public Observable<Response<T>> adapt(Call<T> call) {
return Observable.create(new CallOnSubcribe<>(call));
}
}

static final class SimpleCallAdapter<T> implements CallAdapter<T> {
private final Type responseType;

SimpleCallAdapter(Type responseType) {
this.responseType = responseType;
}

@Override public Type responseType() {
return responseType;
}

@Override public Observable<T> adapt(Call<T> call) {
return Observable.create(new CallOnSubcribe<>(call)) //
.flatMap(new Func1<Response<T>, Observable<T>>() {
@Override public Observable<T> call(Response<T> response) {
if (response.isSuccess()) {
return Observable.just(response.body());
}
return Observable.error(new IOException()); // TODO non-suck message.
}
});
}
}

static final class ResultCallAdapter<T> implements CallAdapter<T> {
private final Type responseType;

ResultCallAdapter(Type responseType) {
this.responseType = responseType;
}

@Override public Type responseType() {
return responseType;
}

@Override public Observable<Result<T>> adapt(Call<T> call) {
return Observable.create(new CallOnSubcribe<>(call)) //
.map(new Func1<Response<T>, Result<T>>() {
@Override public Result<T> call(Response<T> response) {
return Result.fromResponse(response);
}
})
.onErrorReturn(new Func1<Throwable, Result<T>>() {
@Override public Result<T> call(Throwable throwable) {
return Result.fromError(throwable);
}
});
}
}
}
64 changes: 64 additions & 0 deletions retrofit-adapters/rxjava/src/main/java/retrofit/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit;

import java.io.IOException;

import static retrofit.Utils.checkNotNull;

/** The result of executing an HTTP request. */
public final class Result<T> {
static <T> Result<T> fromError(Throwable error) {
return new Result<>(null, checkNotNull(error, "error == null"));
}

static <T> Result<T> fromResponse(Response<T> response) {
return new Result<>(checkNotNull(response, "response == null"), null);
}

private final Response<T> response;
private final Throwable error;

Result(Response<T> response, Throwable error) {
this.response = response;
this.error = error;
}

/**
* The response received from executing an HTTP request. Only present when {@link #isError()} is
* false, null otherwise.
*/
public Response<T> response() {
return response;
}

/**
* The error experienced while attempting to execute an HTTP request. Only present when {@link
* #isError()} is true, null otherwise.
* <p>
* If the error is an {@link IOException} then there was a problem with the transport to the
* remote server. Any other exception type indicates an unexpected failure and should be
* considered fatal (configuration error, programming error, etc.).
*/
public Throwable error() {
return error;
}

/** {@code true} if the request resulted in an error. See {@link #error()} for the cause. */
public boolean isError() {
return error != null;
}
}
Loading

0 comments on commit 2ca1950

Please sign in to comment.