Hello guys, trying to convert `Retrofit` responses...
# arrow
r
Hello guys, trying to convert
Retrofit
responses to
arrow-kt
Either monad:
Copy code
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:
Copy code
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?
r
You can find more file and classes related to the adapter in the same folder
r
@raulraja thanks! This is about
call
adapter factory, that's good, but I'm trying to extract
Either
from my
Json
object, which either has
error
field or
data
field...
I use standard
CouroutinesAdapterFactory
for retrofit calls -> suspend functions
r
I see, sorry I thought this was related to the call adapter. Not sure why it’s failing but it seems by the trace is the actual Gson adapter. You may need a gson type adapter but I don’t use Gson, just a shot in the air 🙂
😄 1
r
@raulraja since I wanted to unwrap JSONRpc envelope, which looks like
Copy code
data 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:
Copy code
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)
            }
        }
    }
...
}
r
nice!