From 6c8f4fe9eef55bb158807c7810e71832540013ce Mon Sep 17 00:00:00 2001 From: Lukasz Kalnik Date: Tue, 4 Jan 2022 16:16:09 +0100 Subject: [PATCH 1/2] Allow null response body if service method returns Unit as body type --- .../adapter/either/ArrowEitherCallAdapter.kt | 8 +++++--- .../adapter/either/ArrowResponseECallAdapter.kt | 14 +++++++------- .../adapter/either/EitherCallAdapterFactory.kt | 5 ++++- .../arrow/retrofit/adapter/either/eitherAdapter.kt | 11 ++++++++++- .../adapter/either/ArrowEitherCallAdapterTest.kt | 8 ++++++++ .../adapter/either/ArrowResponseEAdapterTest.kt | 11 +++++++++++ .../adapter/retrofit/SuspedApiClientTest.kt | 8 ++++++++ 7 files changed, 53 insertions(+), 12 deletions(-) diff --git a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapter.kt b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapter.kt index 0127db812aa..d60464a6946 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapter.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapter.kt @@ -23,13 +23,14 @@ internal class ArrowEitherCallAdapter( private val errorConverter: Converter = retrofit.responseBodyConverter(errorType, arrayOfNulls(0)) - override fun adapt(call: Call): Call> = EitherCall(call, errorConverter) + override fun adapt(call: Call): Call> = EitherCall(call, errorConverter, bodyType) override fun responseType(): Type = bodyType class EitherCall( private val original: Call, - private val errorConverter: Converter + private val errorConverter: Converter, + private val bodyType: Type ) : Call> { override fun enqueue(callback: Callback>) { @@ -44,6 +45,7 @@ internal class ArrowEitherCallAdapter( callback, this@EitherCall, errorConverter, + bodyType, response, { body, _ -> Response.success(response.code(), body.right()) @@ -60,7 +62,7 @@ internal class ArrowEitherCallAdapter( override fun timeout(): Timeout = original.timeout() - override fun clone(): Call> = EitherCall(original.clone(), errorConverter) + override fun clone(): Call> = EitherCall(original.clone(), errorConverter, bodyType) override fun isCanceled(): Boolean = original.isCanceled diff --git a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowResponseECallAdapter.kt b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowResponseECallAdapter.kt index 45181d9b436..c1157393401 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowResponseECallAdapter.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/ArrowResponseECallAdapter.kt @@ -1,6 +1,5 @@ package arrow.retrofit.adapter.either -import arrow.core.Either import arrow.core.left import arrow.core.right import okhttp3.Request @@ -23,13 +22,14 @@ internal class ArrowResponseECallAdapter( private val errorConverter: Converter = retrofit.responseBodyConverter(errorType, arrayOfNulls(0)) - override fun adapt(call: Call): Call> = ResponseECall(call, errorConverter) + override fun adapt(call: Call): Call> = ResponseECall(call, errorConverter, bodyType) override fun responseType(): Type = bodyType class ResponseECall( private val original: Call, - private val errorConverter: Converter + private val errorConverter: Converter, + private val bodyType: Type ) : Call> { override fun enqueue(callback: Callback>) { @@ -44,13 +44,13 @@ internal class ArrowResponseECallAdapter( callback, this@ResponseECall, errorConverter, + bodyType, response, { body, responseT -> - val bodyE: Either = body.right() - Response.success(responseT.code(), ResponseE(responseT.raw(), bodyE)) + Response.success(responseT.code(), ResponseE(responseT.raw(), body.right())) }, { errorBody, responseV -> - Response.success>(ResponseE(responseV.raw(), errorBody.left())) + Response.success(ResponseE(responseV.raw(), errorBody.left())) } ) } @@ -61,7 +61,7 @@ internal class ArrowResponseECallAdapter( override fun timeout(): Timeout = original.timeout() - override fun clone(): Call> = ResponseECall(original.clone(), errorConverter) + override fun clone(): Call> = ResponseECall(original.clone(), errorConverter, bodyType) override fun isCanceled(): Boolean = original.isCanceled diff --git a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/EitherCallAdapterFactory.kt b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/EitherCallAdapterFactory.kt index 16befc18d91..edc347c5de4 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/EitherCallAdapterFactory.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/EitherCallAdapterFactory.kt @@ -56,7 +56,10 @@ public class EitherCallAdapterFactory : CallAdapter.Factory() { } } - private fun eitherAdapter(returnType: ParameterizedType, retrofit: Retrofit): CallAdapter>? { + private fun eitherAdapter( + returnType: ParameterizedType, + retrofit: Retrofit + ): CallAdapter>? { val wrapperType = getParameterUpperBound(0, returnType) return when (getRawType(wrapperType)) { Either::class.java -> { diff --git a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/eitherAdapter.kt b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/eitherAdapter.kt index 6965c06b587..79c20e4dd1b 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/eitherAdapter.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/main/kotlin/arrow/retrofit/adapter/either/eitherAdapter.kt @@ -5,11 +5,13 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Converter import retrofit2.Response +import java.lang.reflect.Type internal inline fun onResponseFn( callback: Callback, call: Call, errorConverter: Converter, + bodyType: Type, response: Response, newResponseFn: (R, Response) -> Response, errorResponseFn: (E, Response) -> Response @@ -17,7 +19,14 @@ internal inline fun onResponseFn( if (response.isSuccessful) { val body = response.body() if (body == null) { - callback.onFailure(call, IllegalStateException("Null body found!")) + // if we defined Unit as body type it means we expected no response body + // e.g. in case of 204 No Content + if (bodyType == Unit::class.java) { + @Suppress("UNCHECKED_CAST") + callback.onResponse(call, newResponseFn(Unit as R, response)) + } else { + callback.onFailure(call, IllegalStateException("Null body found!")) + } } else { callback.onResponse(call, newResponseFn(body, response)) } diff --git a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt index 3089af5b731..157c02065dd 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt @@ -39,6 +39,14 @@ class ArrowEitherCallAdapterTest : UnitSpec() { body shouldBe ResponseMock("Arrow rocks").right() } + "should return Unit when service method returns Unit and null body received" { + server.enqueue(MockResponse().setResponseCode(204)) + + val body = service.postSomething("Sample string") + + body shouldBe Unit.right() + } + "should return ErrorMock for 400 with valid JSON" { server.enqueue(MockResponse().setBody("""{"errorCode":666}""").setResponseCode(400)) diff --git a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt index 9681c76704f..3420c46c152 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt @@ -42,6 +42,17 @@ class ArrowResponseEAdapterTest : UnitSpec() { } } + "should return Unit when service method returns Unit and null body received" { + server.enqueue(MockResponse().setResponseCode(204)) + + val responseE = service.postSomethingResponseE("Sample string") + + with(responseE) { + code shouldBe 204 + body shouldBe Unit.right() + } + } + "should return ErrorMock for 400 with valid JSON" { server.enqueue(MockResponse().setBody("""{"errorCode":42}""").setResponseCode(400)) diff --git a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/retrofit/SuspedApiClientTest.kt b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/retrofit/SuspedApiClientTest.kt index d884ceed91d..e604c1c24bd 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/retrofit/SuspedApiClientTest.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/retrofit/SuspedApiClientTest.kt @@ -4,13 +4,21 @@ import arrow.core.Either import arrow.retrofit.adapter.either.ResponseE import arrow.retrofit.adapter.mock.ErrorMock import arrow.retrofit.adapter.mock.ResponseMock +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST interface SuspedApiClientTest { @GET("/") suspend fun getEither(): Either + @POST("/") + suspend fun postSomething(@Body something: String): Either + @GET("/") suspend fun getResponseE(): ResponseE + + @POST("/") + suspend fun postSomethingResponseE(@Body something: String): ResponseE } From 6e400d6078290b1c45b5eec330d52ada0d716d65 Mon Sep 17 00:00:00 2001 From: Lukasz Kalnik Date: Tue, 4 Jan 2022 16:46:26 +0100 Subject: [PATCH 2/2] Add test for method returning Unit and non-null body received --- .../adapter/either/ArrowEitherCallAdapterTest.kt | 8 ++++++++ .../adapter/either/ArrowResponseEAdapterTest.kt | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt index 157c02065dd..3c351ce0e64 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowEitherCallAdapterTest.kt @@ -47,6 +47,14 @@ class ArrowEitherCallAdapterTest : UnitSpec() { body shouldBe Unit.right() } + "should return Unit when service method returns Unit and JSON body received" { + server.enqueue(MockResponse().setBody("""{"response":"Arrow rocks"}""")) + + val body = service.postSomething("Sample string") + + body shouldBe Unit.right() + } + "should return ErrorMock for 400 with valid JSON" { server.enqueue(MockResponse().setBody("""{"errorCode":666}""").setResponseCode(400)) diff --git a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt index 3420c46c152..306b3020f39 100644 --- a/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt +++ b/arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/ArrowResponseEAdapterTest.kt @@ -53,6 +53,17 @@ class ArrowResponseEAdapterTest : UnitSpec() { } } + "should return Unit when service method returns Unit and JSON body received" { + server.enqueue(MockResponse().setBody("""{"response":"Arrow rocks"}""")) + + val responseE = service.postSomethingResponseE("Sample string") + + with(responseE) { + code shouldBe 200 + body shouldBe Unit.right() + } + } + "should return ErrorMock for 400 with valid JSON" { server.enqueue(MockResponse().setBody("""{"errorCode":42}""").setResponseCode(400))