coroutines x Retrofitで CallAdapterを使いAPIエラーレスポンスをデコードする

## はじめに
APIのエラーレスポンスの型が統一されている場合, エラーレスポンスの型を定義し, デコードを共通化して行いたくなります.
今回は[Retrofit](https://github.com/square/retrofit)の`CallAdapter`を使用して, APIエラー時の挙動を(`HttpException`ではなく) Custom Exceptionを吐くよう変更する方法をメモしておきます.

[公式のサンプル](https://github.com/square/retrofit/tree/master/samples/src/main/java/com/example/retrofit)にも同じ目的の`CallAdapter`がありますが, coroutinesの場合必要な定義が少し変わるのでメモしておきます. 

### CallAdapter.Factory#getの処理
suspend関数を使う場合, `CallAdapter.Factory#get` で `returnType`が`Call`であることを確認する必要があります.

```kotlin
class ErrorHandlingCallAdapterFactory : CallAdapter.Factory() {
    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        // 返り値がCall<..>である場合のみ適用する.
        // 注: 「nullを返す」 == 「このAdapterはこの関数に適用できない」を表す
        if (getRawType(returnType) != Call::class.java) {
            return null
        }
        require(returnType is ParameterizedType) { "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>" }
        ...
    }
}
```

coroutinesを使用する場合, 返り値の型は`Call`で括らず, `suspend fun sampleApi(): Hoge` のように直接定義すると思います. 
そのため一瞬解せない気持ちになりそうですが, この時内部では以下のような処理が行われています([参考](https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/HttpServiceMethod.java#L44-L67)).

- もしsuspend関数であれば, `Hoge`を`Call<Hoge>` に変換する.
- `Call<Hoge>`に対応できる`CallAdapter.Factory`を探索する 

これらから, suspend関数の`returnType`は`Hoge`ではなく`Call<Hoge>`が与えられます. そのため, suspend関数に対応して`CallAdapter`を設定したい場合は, `returnType`が`Call<..>`である時にインスタンスを返すように実装します.

### CallAdapterの型
suspend関数を使う場合, `Call`を返すAdapterを適用します.

```kotlin
    class Adapter<T> : CallAdapter<T, Call<T>> {
        override fun responseType(): Type = ...
        // Call<T>を受け取り, Call<T>を返すAdapter.
        override fun adapt(call: Call<T>): Call<T> = ..
    }
```

前節と同様に, retrofit内部では以下のような処理がされています([参考](https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/HttpServiceMethod.java#L202-L236)).

- `CallAdapter#adopt`の返り値として`Call<Hoge>`を受け取る
-  それを`Contiuation` (suspend関数) を介して`Hoge` に変換して返す

そのため, 「(`Call<T>`を受け取り, )`Call<T>`を返す」型である `CallAdapter<T, Call<T>>`が正しい型となります.

### Executor
suspend関数を使う場合, 通常callbackをメインスレッドで行う必要がないため, `Retrofit#callbackExecutor`は使用しません.
メインスレッド以外を設定していたり, 特別な事情がある場合は使用した方が良さそうです.

### サンプル
以下がコード例です. Custom Exceptionにデコードできないケースではデフォルトと同じ挙動をさせるような実装になっています.

```kotlin
class ErrorHandlingCallAdapterFactory : CallAdapter.Factory() {
    override fun get(
        returnType: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): CallAdapter<*, *>? {
        if (getRawType(returnType) != Call::class.java) {
            return null
        }
        require(returnType is ParameterizedType) { "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>" }
        return Adapter<Any>(getParameterUpperBound(0, returnType))
    }

    private class Adapter<T>(private val responseType: Type) : CallAdapter<T, Call<T>> {
        override fun responseType(): Type = responseType
        override fun adapt(call: Call<T>): Call<T> = ErrorHandlingCall(call)
    }
}



class ErrorHandlingCall<T>(
    private val delegate: Call<T>
) : Call<T> by delegate {
    override fun enqueue(callback: Callback<T>) =
        delegate.enqueue(object : Callback<T> by callback {
            override fun onResponse(call: Call<T>, response: Response<S>) {
                if (response.isSuccessful) {
                    callback.onResponse(this@ErrorHandlingCall, response)
                    return
                }
                val errorBody = response.errorBody()
                if (errorBody == null || errorBody.contentLength() == 0L) {
                    callback.onResponse(this@ErrorHandlingCall, response)
                    return
                }

                try {
                    // Custom Exceptionにデコード・変換する
                    val exception = convertToCustomException(response)
                    callback.onFailure(this@ErrorHandlingCall, exception)
                } catch (ex: Exception) {
                    callback.onResponse(this@ErrorHandlingCall, response)
                }
            }
        })
}
```

これを`Retrofit.Builder`に渡せば完成です.

```kotlin

interface Service {
    @GET("..sample..")
    suspend fun sample(): Sample
}

Retrofit.Builder()
    .addCallAdapterFactory(ErrorHandlingCallAdapterFactory())
    .build()
    .create(Service::class.java)
```

## 参考
- https://square.github.io/retrofit/2.x/retrofit/retrofit2/CallAdapter.html
- https://github.com/square/retrofit