forked from arrow-kt/arrow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
EitherCallAdapterFactory.kt
94 lines (85 loc) · 2.93 KB
/
EitherCallAdapterFactory.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package arrow.retrofit.adapter.either
import arrow.core.Either
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
/**
* A [CallAdapter.Factory] which supports suspend + [Either] as the return type
*
* Adding this to [Retrofit] will enable you to return [Either] from your service methods.
*
* ```kotlin
* import arrow.core.Either
* import retrofit2.http.GET
*
* data class User(val name: String)
* data class ErrorBody(val msg: String)
* interface MyService {
* @GET("/user/me")
* suspend fun user(): Either<ErrorBody, User>
*
* @GET("/user/me")
* suspend fun userResponse(): Either<ErrorBody, User>
* }
* ```
* <!--- KNIT example-arrow-retrofit-01.kt -->
*
* Using [Either] as the return type means that 200 status code and HTTP errors return a value,
* other exceptions will throw.
*
* [ResponseE] is similar to [retrofit2.Response] but uses [Either] for the response body.
*/
public class EitherCallAdapterFactory : CallAdapter.Factory() {
public companion object {
public fun create(): EitherCallAdapterFactory = EitherCallAdapterFactory()
}
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
val rawType = getRawType(returnType)
if (returnType !is ParameterizedType) {
val name = parseTypeName(returnType)
throw IllegalArgumentException(
"Return type must be parameterized as " +
"$name<Foo> or $name<out Foo>"
)
}
return when (rawType) {
Call::class.java -> eitherAdapter(returnType, retrofit)
else -> null
}
}
private fun eitherAdapter(
returnType: ParameterizedType,
retrofit: Retrofit
): CallAdapter<Type, out Call<out Any>>? {
val wrapperType = getParameterUpperBound(0, returnType)
return when (getRawType(wrapperType)) {
Either::class.java -> {
val (errorType, bodyType) = extractErrorAndReturnType(wrapperType, returnType)
ArrowEitherCallAdapter<Any, Type>(retrofit, errorType, bodyType)
}
ResponseE::class.java -> {
val (errorType, bodyType) = extractErrorAndReturnType(wrapperType, returnType)
ArrowResponseECallAdapter<Any, Type>(retrofit, errorType, bodyType)
}
else -> null
}
}
private inline fun extractErrorAndReturnType(wrapperType: Type, returnType: ParameterizedType): Pair<Type, Type> {
if (wrapperType !is ParameterizedType) {
val name = parseTypeName(returnType)
throw IllegalArgumentException(
"Return type must be parameterized as " +
"$name<ErrorBody, ResponseBody> or $name<out ErrorBody, out ResponseBody>"
)
}
val errorType = getParameterUpperBound(0, wrapperType)
val bodyType = getParameterUpperBound(1, wrapperType)
return Pair(errorType, bodyType)
}
}
private fun parseTypeName(type: Type) =
type.toString()
.split(".")
.last()