rcd27
05/28/2021, 8:21 AMRetrofit
responses to arrow-kt
Either monad:
class JSONRpcResponseEnvelopeConverter<T>(
private val delegate: Converter<ResponseBody, JSONRpcResponseEnvelope<Any>>
) : Converter<ResponseBody, Either<Throwable, T>> {
override fun convert(value: ResponseBody): Either<Throwable, T> {
val response = delegate.convert(value)
val error = response?.error
return if (error == null) {
Either.Right(
response?.result as T
)
} else {
Either.Left(
InternalServerException(response.error)
)
}
}
}
But it fails with:
java.lang.RuntimeException: Failed to invoke private arrow.core.Either() with no args
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:113)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
at com.tochka.bank.npd.core.network.JSONRpcResponseEnvelopeConverter.convert(JSONRpcResponseEnvelopeConverter.kt:12)
at com.tochka.bank.npd.core.network.JSONRpcResponseEnvelopeConverter.convert(JSONRpcResponseEnvelopeConverter.kt:7)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Caused by: java.lang.InstantiationException: Can't instantiate abstract class arrow.core.Either
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:110)
... 13 more
Maybe there are already proper converters for retrofit2 to arrow-kt?raulraja
05/28/2021, 8:29 AMarrow-core-retrofit
in 0.13.2 which includes an adapter for Either https://github.com/arrow-kt/arrow/blob/5a3037b4563289ec3402bfe0006b3affcb82799a/ar[…]otlin/arrow/retrofit/adapter/either/EitherCallAdapterFactory.ktraulraja
05/28/2021, 8:31 AMrcd27
05/28/2021, 8:38 AMcall
adapter factory, that's good, but I'm trying to extract Either
from my Json
object, which either has error
field or data
field...rcd27
05/28/2021, 8:39 AMCouroutinesAdapterFactory
for retrofit calls -> suspend functionsraulraja
05/28/2021, 8:43 AMraulraja
05/28/2021, 8:44 AMrcd27
06/01/2021, 1:31 PMdata class JSONRpcResponseEnvelope<T>(
val id: String,
val jsonrpc: String,
val result: T?,
val error: ApiError?
)
Into Either<ApiError, T>
(so the retorfit's interface functions look like fun getUser(...): Either<ApiError, User>
), the solution was neither take EitherCallAdapter
or implementing GSON
deserialization to Either
. I added ConverterFactory
with Gson
delegate. And just checked type parameters. Solution looks like:
class JSONRpcEnvelopeConverterFactory : Converter.Factory() {
private val gsonConverterFactory = GsonConverterFactory.create(
GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
)
override fun responseBodyConverter(
type: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, Either<ApiError, Any>> {
val typeOfInterest =
(type as ParameterizedType).actualTypeArguments[1] // We take second parameter, because first one is always `ApiError`
val wrappedType = object : ParameterizedType {
override fun getRawType(): Type = JSONRpcResponseEnvelope::class.java
override fun getOwnerType(): Type? = null
override fun getActualTypeArguments(): Array<Type> = arrayOf(typeOfInterest)
}
// Delegate deserializing to GSON
val gsonConverter = gsonConverterFactory
.responseBodyConverter(wrappedType, annotations, retrofit)
return Converter<ResponseBody, Either<ApiError, Any>> { value ->
val response =
(gsonConverter as Converter<ResponseBody, JSONRpcResponseEnvelope<Any>>).convert(
value
)
val error = response?.error
if (error == null) {
Either.Right(response?.result as Any)
} else {
Either.Left(response.error)
}
}
}
...
}
raulraja
06/01/2021, 2:31 PM