Created
August 31, 2021 08:22
-
-
Save Aidanvii7/3a3246b4ca43b6fce6318ecede5655d6 to your computer and use it in GitHub Desktop.
A CallAdapter for the built in Result type
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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