-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Closed
Description
With following sample code I get above error when kotlin.Result is used with a suspend function and a runtime exception is thrown in the Retrofit call factory - this works though with e.g. sealed or data classes.
class CrashingResultCallAdapterTest {
@get:Rule val server = MockWebServer()
@Test
fun crash(): Unit = runBlocking {
val retrofit = Retrofit.Builder()
.baseUrl(server.url("/"))
.addConverterFactory(StringConverterFactory())
.addCallAdapterFactory(SuspendResultCallAdapterFactory())
.callFactory { throw IllegalStateException("some error") }
.build()
val service = retrofit.create(ResultService::class.java)
server.enqueue(
MockResponse()
.setResponseCode(200)
.setHeader("content-type", "text/plain")
.setBody("bar")
)
assertThat(service.fooBarResult().getOrThrow()).isEqualTo("bar")
}
@Test
fun success(): Unit = runBlocking {
val retrofit = Retrofit.Builder()
.baseUrl(server.url("/"))
.addConverterFactory(StringConverterFactory())
.addCallAdapterFactory(SuspendResultCallAdapterFactory())
.build()
val service = retrofit.create(ResultService::class.java)
server.enqueue(
MockResponse()
.setResponseCode(200)
.setHeader("content-type", "text/plain")
.setBody("bar")
)
assertThat(service.fooBarResult().getOrThrow()).isEqualTo("bar")
server.enqueue(
MockResponse()
.setResponseCode(404)
.setHeader("content-type", "text/plain")
.setBody("barerror")
)
assertThat(service.fooBarResult().exceptionOrNull()).isInstanceOf(HttpException::class.java)
server.enqueue(MockResponse().setSocketPolicy(DISCONNECT_AFTER_REQUEST))
assertThat(service.fooBarResult().exceptionOrNull()).isInstanceOf(SocketTimeoutException::class.java)
}
private interface ResultService {
@GET("/")
suspend fun fooBarResult(): Result<String>
}
private class StringConverterFactory : Converter.Factory() {
override fun requestBodyConverter(
type: Type, parameterAnnotations: Array<out Annotation>, methodAnnotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<*, RequestBody> {
return Converter<String, RequestBody> { value -> value.toRequestBody("text/plain".toMediaType()) }
}
override fun responseBodyConverter(
type: Type, annotations: Array<out Annotation>, retrofit: Retrofit
): Converter<ResponseBody, *> {
return Converter<ResponseBody, String> { value -> value.string() }
}
}
private class SuspendResultCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != Call::class.java) return null
if (returnType !is ParameterizedType) return null
val resultType: Type = getParameterUpperBound(0, returnType)
if (getRawType(resultType) != Result::class.java || resultType !is ParameterizedType
) return null
val delegate: CallAdapter<*, *> = retrofit.nextCallAdapter(this, returnType, annotations)
return CatchingCallAdapter(delegate)
}
private class CatchingCallAdapter(
private val delegate: CallAdapter<*, *>,
) : CallAdapter<Any, Call<Result<*>>> {
override fun responseType(): Type = delegate.responseType()
override fun adapt(call: Call<Any>): Call<Result<*>> = CatchingCall(call)
}
private class CatchingCall(
private val delegate: Call<Any>,
) : Call<Result<*>> {
override fun enqueue(callback: Callback<Result<*>>) = delegate.enqueue(object : Callback<Any> {
override fun onResponse(call: Call<Any>, response: Response<Any>) {
if (response.isSuccessful) {
val body = response.body()
callback.onResponse(this@CatchingCall, Response.success(Result.success(body)))
} else {
val throwable = HttpException(response)
callback.onResponse(this@CatchingCall, Response.success(Result.failure<Any>(throwable)))
}
}
override fun onFailure(call: Call<Any>, t: Throwable) {
callback.onResponse(this@CatchingCall, Response.success(Result.failure<Any>(t)))
}
})
override fun clone(): Call<Result<*>> = CatchingCall(delegate)
override fun execute(): Response<Result<*>> =
throw UnsupportedOperationException("Suspend function should not be blocking.")
override fun isExecuted(): Boolean = delegate.isExecuted
override fun cancel(): Unit = delegate.cancel()
override fun isCanceled(): Boolean = delegate.isCanceled
override fun request(): Request = delegate.request()
override fun timeout(): Timeout = delegate.timeout()
}
}
}I'm not sure what the root cause here is.
shaveensalih and nlg521