Skip to content

Instantly share code, notes, and snippets.

@nomisRev
Last active August 4, 2018 19:36
Show Gist options
  • Save nomisRev/539dc9f7c306571e0bf6405f440efc2d to your computer and use it in GitHub Desktop.
Save nomisRev/539dc9f7c306571e0bf6405f440efc2d to your computer and use it in GitHub Desktop.
Retrofit Call Adapter
import arrow.InstanceParametrizedType
import arrow.instance
import retrofit2.Converter
import okhttp3.ResponseBody
import helios.core.Json
import retrofit2.Retrofit
import java.lang.reflect.Type
import okhttp3.RequestBody
import helios.typeclasses.Encoder
import okhttp3.MediaType
object HeliosConverterFactory {
@JvmStatic
@JvmName("create")
operator fun invoke() = object : Converter.Factory() {
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit) = HeliosResponseBodyConverter
override fun requestBodyConverter(type: Type, parameterAnnotations: Array<out Annotation>, methodAnnotations: Array<out Annotation>?, retrofit: Retrofit): Converter<*, RequestBody>? {
val encoder: Encoder<*> = instance(InstanceParametrizedType(Encoder::class.java, listOf(type)))
return heliosRequesBodyConverter(encoder)
}
}
}
private object HeliosResponseBodyConverter : Converter<ResponseBody, Json> {
override fun convert(value: ResponseBody): Json = value.use {
Json.parseFromString(value.string()).fold({ throw it }, { it })
}
}
private val MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8")
private fun <T> heliosRequesBodyConverter(encoder: Encoder<T>) = Converter<T, RequestBody> { value ->
RequestBody.create(MEDIA_TYPE, encoder.encode(value).toJsonString())
}
import arrow.InstanceParametrizedType
import arrow.instance
import retrofit2.Converter
import okhttp3.ResponseBody
import helios.core.Json
import retrofit2.Retrofit
import java.lang.reflect.Type
import okhttp3.RequestBody
import helios.typeclasses.Encoder
import okhttp3.MediaType
inline fun <reified F> heliosCallAdapterFactory(AE: ApplicativeError<F, Throwable> = applicativeError()) = object : CallAdapter.Factory() {
override fun get(returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit): CallAdapter<*, *>? =
if (rawType(returnType) == HK::class.java) createBodyCallAdapter(returnType, AE)
else null
inline fun <reified F> createBodyCallAdapter(returnType: Type, AE: ApplicativeError<F, Throwable>): CallAdapter<Json, HK<F, Json>>? {
val (left, _) = (returnType as? ParameterizedType)
?.let { parameterUpperBound(0, it) to parameterUpperBound(1, it) }
?.takeIf { (_, right) -> right == Json::class.java }
?: throw IllegalStateException("HK return type must be parameterized as HK<F, Json>")
val newLeft = left?.let(::findF)
if (newLeft != F::class.java) return null
return object : CallAdapter<Json, HK<F, Json>> {
override fun responseType(): Type = Json::class.java
override fun adapt(call: Call<Json>): HK<F, Json> = AE.catch {
val response = call.execute()
if (response.isSuccessful) response.body()!!
else throw HttpException(response)
}
}
}
private tailrec fun findF(type: Type?): Type? {
return if ((type as? ParameterizedType)?.rawType == HK::class.java) {
val unwrappedType = (type as? ParameterizedType)?.let { parameterUpperBound(0, it) }
findF(unwrappedType)
}
else type
}
private fun parameterUpperBound(index: Int, type: ParameterizedType): Type? {
val types = type.actualTypeArguments
if (index < 0 || index >= types.size) return null
val paramType = types[index]
return if (paramType is WildcardType) paramType.upperBounds[0] else paramType
}
private fun rawType(type: Type): Class<*>? = when (type) {
is Class<*> -> type
is ParameterizedType -> type.rawType as? Class<*>
is GenericArrayType -> java.lang.reflect.Array.newInstance(rawType(type.genericComponentType), 0).javaClass
is TypeVariable<*> -> Any::class.java
is WildcardType -> rawType(type.upperBounds[0])
else -> null
}
}
import arrow.core.EitherHK
import arrow.core.EitherKind
import arrow.core.ev
import arrow.data.TryHK
import arrow.data.TryKind
import arrow.data.ev
import arrow.effects.IO
import arrow.effects.IOHK
import arrow.effects.IOKind
import arrow.effects.ObservableKW
import arrow.effects.ObservableKWHK
import arrow.effects.ObservableKWKind
import arrow.effects.ev
import helios.core.Json
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
import retrofit2.Retrofit
interface TravisNetworkService {
@Headers("Accept: application/vnd.travis-ci.2+json", "User-Agent: android")
@GET("repos/{username}")
fun listRepos(@Path("username") username: String): TryKind<Json>
@Headers("Accept: application/vnd.travis-ci.2+json", "User-Agent: android")
@GET("repos/{repoId}")
fun repo(@Path("repoId") repoId: String): EitherKind<Throwable, Json>
@Headers("Accept: application/vnd.travis-ci.2+json", "User-Agent: android")
@GET("repos/{repoId}")
fun ioRepo(@Path("repoId") repoId: String): IOKind<Json>
@Headers("Accept: application/vnd.travis-ci.2+json", "User-Agent: android")
@GET("repos/{repoId}")
fun rxRepo(@Path("repoId") repoId: String): ObservableKWKind<Json>
}
fun main(args: Array<String>) {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.travis-ci.org/")
.addConverterFactory(HeliosConverterFactory())
.addCallAdapterFactory(heliosCallAdapterFactory<TryHK>())
.addCallAdapterFactory(heliosCallAdapterFactory<EitherHK>())
.addCallAdapterFactory(heliosCallAdapterFactory<IOHK>())
.addCallAdapterFactory(heliosCallAdapterFactory<ObservableKWHK>())
.build()
val service = retrofit.create(TravisNetworkService::class.java)
service.listRepos("arrow-kt").ev()
.fold(::println, ::println)
service.repo("12690991")
.ev()
.fold(::println, ::println)
service.ioRepo("0")
.let(::println)
service.rxRepo("0")
.ev()
.observable
.subscribe(::println, ::println)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment