Skip to content

Instantly share code, notes, and snippets.

@Aidanvii7
Created August 31, 2021 08:22
Show Gist options
  • Save Aidanvii7/3a3246b4ca43b6fce6318ecede5655d6 to your computer and use it in GitHub Desktop.
Save Aidanvii7/3a3246b4ca43b6fce6318ecede5655d6 to your computer and use it in GitHub Desktop.
A CallAdapter for the built in Result type
import okhttp3.ResponseBody
import retrofit2.*
import java.io.IOException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
internal class ResultCallAdapterFactory(
private val errorResponseConverterFactory: Converter.Factory,
private val errorType: Type,
) : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit,
): CallAdapter<*, *>? {
if (getRawType(returnType) != Call::class.java) return null
check(returnType is ParameterizedType) { "Return type must be a parameterized type." }
val responseType: Type = getParameterUpperBound(0, returnType)
if (getRawType(responseType) != Result::class.java) return null
check(responseType is ParameterizedType) { "Response type must be a parameterized type." }
val wrappedType = getParameterUpperBound(0, responseType)
return ResultCallAdapter<Any>(
successType = wrappedType,
errorResponseConverter = errorResponseConverterFactory.responseBodyConverter(
errorType,
emptyArray(),
retrofit,
)!!,
)
}
}
private class ResultCallAdapter<SuccessType>(
private val successType: Type,
private val errorResponseConverter: Converter<ResponseBody, *>,
) : CallAdapter<SuccessType, Call<Result<SuccessType>>> {
override fun adapt(call: Call<SuccessType>) = ResultCall(
delegate = call,
successType = successType,
errorResponseConverter = errorResponseConverter,
)
override fun responseType(): Type = successType
}
private class ResultCall<SuccessType>(
private val delegate: Call<SuccessType>,
private val successType: Type,
private val errorResponseConverter: Converter<ResponseBody, *>,
) : Call<Result<SuccessType>> {
override fun enqueue(callback: Callback<Result<SuccessType>>) {
delegate.enqueue(callback.wrapped())
}
private fun Callback<Result<SuccessType>>.wrapped(): Callback<SuccessType> = object : Callback<SuccessType> {
override fun onResponse(
call: Call<SuccessType>,
response: Response<SuccessType>,
) {
onResponse(this@ResultCall, Response.success(response.toResult()))
}
private fun Response<SuccessType>.toResult(): Result<SuccessType> {
// Http error response (4xx - 5xx)
if (!isSuccessful) {
val errorBody: ResponseBody? = errorBody()
val errorMessage: String = if (errorBody != null) {
runCatching {
errorResponseConverter.convert(errorBody).toString()
}.recoverCatching {
errorBody.string()
}.getOrThrow()
} else ""
return Result.failure(ApiError.Http(code(), errorMessage))
}
// Http success response with body
body()?.let { body: SuccessType ->
return Result.success(body)
}
// if we defined Unit as success type it means we expected no response body
// e.g. in case of 204 No Content
return if (successType == Unit::class.java) {
Result.success(Unit) as Result<SuccessType>
} else {
Result.failure(UnknownError("Response body was null"))
}
}
override fun onFailure(
call: Call<SuccessType>,
throwable: Throwable,
) {
val error = when (throwable) {
is IOException -> ApiError.Network(throwable)
else -> ApiError.Unknown(throwable)
}
onResponse(this@ResultCall, Response.success(Result.failure(error)))
}
}
override fun isExecuted() = delegate.isExecuted
override fun clone() = ResultCall<SuccessType>(
delegate = delegate.clone(),
successType = successType,
errorResponseConverter = errorResponseConverter,
)
override fun isCanceled() = delegate.isCanceled
override fun cancel() = delegate.cancel()
override fun execute() = throw UnsupportedOperationException()
override fun request() = delegate.request()
override fun timeout() = delegate.timeout()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment