├── .gitignore ├── README.md ├── build.gradle └── src └── main └── java └── com └── lightningkite └── kotlin └── networking ├── CustomizedTypeAdapterFactory.kt ├── ErrorCaptureApi.kt ├── GsonExtensions.kt ├── MediaTypes.kt ├── MyGson.kt ├── OkHttpApi.kt ├── OkHttpExtensions.kt └── TypedResponse.kt /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kotlin-networking 2 | 3 | This library isn't platform specific. 4 | 5 | This library is a collection of extension functions for OkHttp that enables OkHttp to be used in a way similar to Retrofit without all of the annotation magic. It also includes Gson for receiveConversion to and from JSON. 6 | 7 | ## Key functions and classes 8 | 9 | `OkHttpApi` - Make your API object extends this to use headers common to entire API, as well as a common root. 10 | 11 | `Request.Builder.lambdaGson` - Uses your request builder to create a lambda that executes the request, with the response converted to `T` using Gson. This is useful because it allows you to use `.invokeAsync{ response -> }` from [kotlin-core](https://github.com/lightningkite/kotlin-core). 12 | 13 | `TypedResponse` - A typed network response. Important things it contains: `.isSuccessful()`, `.result`, `.errorString`, `.code`. 14 | 15 | ## Example usage 16 | 17 | ```kotlin 18 | object ExampleAPI : OkHttpApi("https://jsonplaceholder.typicode.com") { 19 | 20 | //This will get from "https://jsonplaceholder.typicode.com/posts" with a return type of List 21 | //getPosts() returns a ()->List 22 | fun getPosts() = requestBuilder("/posts").get().lambdaGson>() 23 | 24 | //This will post `post` to "https://jsonplaceholder.typicode.com/posts" with a return type of Post 25 | fun createPost(post:Post) = requestBuilder("/posts").post(post).lambdaGson() 26 | } 27 | 28 | //The model. Gson takes care of the matching of names to JSON keys. 29 | class Post( 30 | var userId: Long = -1, 31 | var id: Long? = null, 32 | var title: String = "", 33 | var body: String = "" 34 | ) 35 | 36 | fun test(){ 37 | 38 | //synchronous 39 | val response = ExampleAPI.getPosts().invoke() 40 | if(response.isSuccessful()){ 41 | println("Post list obtained. Number of posts: ${response.result!!.size}") 42 | } else { 43 | println("There was an error. ${response.errorString}") 44 | } 45 | 46 | //asynchronous 47 | val post = Post(userId = 3, title = "New Post", body = "This is a new post!") 48 | ExampleApi.createPost(post).invokeAsync{ response -> 49 | if(response.isSuccessful()){ 50 | println("Post creation was successful. Post ID: ${response.result!!.id}") 51 | } else { 52 | println("There was an error. ${response.errorString}") 53 | } 54 | } 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.21' 3 | repositories { 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 8 | } 9 | } 10 | 11 | apply plugin: 'kotlin' 12 | 13 | defaultTasks 'run' 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 21 | testCompile 'junit:junit:4.12' 22 | testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" 23 | compile project(':kotlin-core') 24 | compile 'com.google.code.gson:gson:2.8.2' 25 | compile 'com.squareup.okhttp3:okhttp:3.9.0' 26 | compile 'com.github.salomonbrys.kotson:kotson:2.2.2' 27 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/CustomizedTypeAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonElement 5 | import com.google.gson.TypeAdapter 6 | import com.google.gson.TypeAdapterFactory 7 | import com.google.gson.reflect.TypeToken 8 | import com.google.gson.stream.JsonReader 9 | import com.google.gson.stream.JsonWriter 10 | import java.io.IOException 11 | 12 | abstract class CustomizedTypeAdapterFactory(private val customizedClass: Class) : TypeAdapterFactory { 13 | 14 | @SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal 15 | override fun create(gson: Gson, type: TypeToken): TypeAdapter? { 16 | return if (type.rawType === customizedClass) 17 | customizeMyClassAdapter(gson, type as TypeToken) as TypeAdapter 18 | else 19 | null 20 | } 21 | 22 | private fun customizeMyClassAdapter(gson: Gson, type: TypeToken): TypeAdapter { 23 | val delegate = gson.getDelegateAdapter(this, type) 24 | val elementAdapter = gson.getAdapter(JsonElement::class.java) 25 | return object : TypeAdapter() { 26 | @Throws(IOException::class) 27 | override fun write(out: JsonWriter, value: C?) { 28 | val tree = delegate.toJsonTree(value) 29 | elementAdapter.write(out, tree) 30 | if (value != null) { 31 | afterWrite(value, tree) 32 | } 33 | } 34 | 35 | @Throws(IOException::class) 36 | override fun read(`in`: JsonReader): C? { 37 | val tree = elementAdapter.read(`in`) 38 | return delegate.fromJsonTree(tree)?.apply { 39 | afterRead(this, tree) 40 | } 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Override this to muck with `toSerialize` before it is written to 47 | * the outgoing JSON stream. 48 | */ 49 | protected open fun afterWrite(source: C, toSerialize: JsonElement) { 50 | } 51 | 52 | /** 53 | * Override this to muck with the object after it parsed into 54 | * the application type. 55 | */ 56 | protected open fun afterRead(output: C, deserialized: JsonElement): C { 57 | return output 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/ErrorCaptureApi.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import com.lightningkite.kotlin.async.doUiThread 4 | import com.lightningkite.kotlin.invokeAll 5 | import java.util.* 6 | 7 | /** 8 | * Created by joseph on 11/11/16. 9 | */ 10 | interface ErrorCaptureApi { 11 | 12 | val onError: ArrayList<(TypedResponse<*>) -> Unit> 13 | 14 | fun (() -> TypedResponse).captureError(): () -> TypedResponse { 15 | return { 16 | val response = this.invoke() 17 | if (!response.isSuccessful()) { 18 | doUiThread { 19 | onError.invokeAll(response) 20 | } 21 | } 22 | response 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/GsonExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import com.github.salomonbrys.kotson.fromJson 4 | import com.google.gson.* 5 | import java.lang.reflect.Type 6 | 7 | /** 8 | * Created by josep on 3/3/2016. 9 | */ 10 | 11 | fun Collection.toJsonArray(): JsonArray { 12 | val array = JsonArray() 13 | for (value in this) 14 | array.add(value.toJsonElement()) 15 | return array; 16 | } 17 | 18 | fun Any?.toJsonElement(): JsonElement { 19 | if (this == null) 20 | return JsonNull.INSTANCE 21 | 22 | return when (this) { 23 | is Number -> JsonPrimitive(this) 24 | is Char -> JsonPrimitive(this) 25 | is Boolean -> JsonPrimitive(this) 26 | is String -> JsonPrimitive(this) 27 | is JsonElement -> this 28 | else -> throw IllegalArgumentException("${this} cannot be converted to JSON") 29 | } 30 | } 31 | 32 | fun Any.gsonToJson(gson: Gson = MyGson.gson): JsonElement { 33 | return gson.toJsonTree(this) 34 | } 35 | 36 | fun Any.gsonToString(gson: Gson = MyGson.gson): String { 37 | return gson.toJson(this) 38 | } 39 | 40 | fun Any?.gsonToOptional(gson: Gson = MyGson.gson): String { 41 | return if (this == null) "null" else gson.toJson(this) 42 | } 43 | 44 | val JsonElement.asStringOptional: String? 45 | get() = if (this is JsonPrimitive) asString else null 46 | val JsonElement.asIntOptional: Int? 47 | get() = if (this is JsonPrimitive) asInt else null 48 | 49 | inline fun String.gsonFrom(gson: Gson = MyGson.gson): T? { 50 | try { 51 | return gson.fromJson(this) 52 | } catch(e: JsonParseException) { 53 | e.printStackTrace() 54 | } catch(e: JsonSyntaxException) { 55 | e.printStackTrace() 56 | } catch (e: IllegalStateException) { 57 | e.printStackTrace() 58 | } 59 | return null 60 | } 61 | 62 | inline fun JsonElement.gsonFrom(gson: Gson = MyGson.gson): T? { 63 | try { 64 | return gson.fromJson(this) 65 | } catch(e: JsonParseException) { 66 | e.printStackTrace() 67 | } catch(e: JsonSyntaxException) { 68 | e.printStackTrace() 69 | } catch (e: IllegalStateException) { 70 | e.printStackTrace() 71 | } 72 | return null 73 | } 74 | 75 | @Suppress("NOTHING_TO_INLINE") 76 | inline fun String.gsonFrom(type: Class, gson: Gson = MyGson.gson): T? { 77 | try { 78 | return gson.fromJson(this, type) 79 | } catch(e: JsonParseException) { 80 | e.printStackTrace() 81 | } catch(e: JsonSyntaxException) { 82 | e.printStackTrace() 83 | } 84 | return null 85 | } 86 | 87 | @Suppress("NOTHING_TO_INLINE") 88 | inline fun JsonElement.gsonFrom(type: Class, gson: Gson = MyGson.gson): T? { 89 | try { 90 | return gson.fromJson(this, type) 91 | } catch(e: JsonParseException) { 92 | e.printStackTrace() 93 | } catch(e: JsonSyntaxException) { 94 | e.printStackTrace() 95 | } 96 | return null 97 | } 98 | 99 | @Suppress("NOTHING_TO_INLINE") 100 | inline fun String.gsonFrom(type: Type, gson: Gson = MyGson.gson): T? { 101 | try { 102 | return gson.fromJson(this, type) 103 | } catch(e: JsonParseException) { 104 | e.printStackTrace() 105 | } catch(e: JsonSyntaxException) { 106 | e.printStackTrace() 107 | } 108 | return null 109 | } 110 | 111 | @Suppress("NOTHING_TO_INLINE") 112 | inline fun JsonElement.gsonFrom(type: Type, gson: Gson = MyGson.gson): T? { 113 | try { 114 | return gson.fromJson(this, type) 115 | } catch(e: JsonParseException) { 116 | e.printStackTrace() 117 | } catch(e: JsonSyntaxException) { 118 | e.printStackTrace() 119 | } 120 | return null 121 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/MediaTypes.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import okhttp3.MediaType 4 | 5 | /** 6 | * Created by josep on 11/10/2016. 7 | */ 8 | 9 | 10 | object MediaTypes { 11 | val JSON = MediaType.parse("application/json; charset=utf-8") 12 | val TEXT = MediaType.parse("text/plain; charset=utf-8") 13 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/MyGson.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import com.google.gson.* 4 | import java.util.* 5 | 6 | /** 7 | * Created by jivie on 8/13/15. 8 | */ 9 | 10 | object MyGson { 11 | 12 | private val hierarchyAdapters = ArrayList, Any>>() 13 | fun registerHierarchy(type: Class<*>, adapter: Any) { 14 | hierarchyAdapters += type to adapter 15 | update() 16 | } 17 | 18 | inline fun registerHierarchy(adapter: JsonDeserializer) = registerHierarchy(T::class.java, adapter) 19 | inline fun registerHierarchy(adapter: JsonSerializer) = registerHierarchy(T::class.java, adapter) 20 | inline fun registerHierarchy(adapter: TypeAdapter) = registerHierarchy(T::class.java, adapter) 21 | 22 | private val adapters = ArrayList, Any>>() 23 | fun register(type: Class<*>, adapter: Any) { 24 | adapters += type to adapter 25 | update() 26 | } 27 | 28 | inline fun register(adapter: JsonDeserializer) = register(T::class.java, adapter) 29 | inline fun register(adapter: JsonSerializer) = register(T::class.java, adapter) 30 | inline fun register(adapter: TypeAdapter) = register(T::class.java, adapter) 31 | inline fun registerNullable(adapter: JsonDeserializer) = register(T::class.java, adapter) 32 | inline fun registerNullable(adapter: JsonSerializer) = register(T::class.java, adapter) 33 | inline fun registerNullable(adapter: TypeAdapter) = register(T::class.java, adapter) 34 | 35 | private val factories = ArrayList() 36 | fun registerFactory(factory: TypeAdapterFactory) { 37 | factories.add(factory) 38 | update() 39 | } 40 | 41 | val json: JsonParser = JsonParser() 42 | 43 | private var gsonInternal: Gson? = null 44 | val gson: Gson get() = gsonInternal ?: initialize() 45 | 46 | fun update() { 47 | if (gsonInternal == null) return 48 | initialize() 49 | } 50 | 51 | fun initialize(): Gson { 52 | 53 | val builder = GsonBuilder() 54 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 55 | for (factory in factories) { 56 | builder.registerTypeAdapterFactory(factory) 57 | } 58 | for ((type, adapter) in hierarchyAdapters) { 59 | builder.registerTypeHierarchyAdapter(type, adapter) 60 | } 61 | for ((type, adapter) in adapters) { 62 | builder.registerTypeAdapter(type, adapter) 63 | } 64 | val gson = builder.create() 65 | gsonInternal = gson 66 | return gson 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/OkHttpApi.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import okhttp3.Request 4 | 5 | /** 6 | * Created by joseph on 11/10/2016. 7 | */ 8 | interface OkHttpApi { 9 | val baseUrl: String 10 | val headers: List> get() = listOf() 11 | 12 | 13 | fun requestBuilder(urlFromBase: String): Request.Builder { 14 | val builder = Request.Builder() 15 | .url(baseUrl + urlFromBase) 16 | for (header in headers) { 17 | builder.header(header.first, header.second) 18 | } 19 | return builder 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/OkHttpExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import com.github.salomonbrys.kotson.fromJson 4 | import com.google.gson.Gson 5 | import com.google.gson.JsonElement 6 | import com.lightningkite.kotlin.stream.writeToFile 7 | import okhttp3.* 8 | import okhttp3.internal.Util 9 | import okio.BufferedSink 10 | import okio.Okio 11 | import okio.Source 12 | import java.io.File 13 | import java.io.InputStream 14 | import java.lang.reflect.Type 15 | 16 | /** 17 | * 18 | * Created by josep on 11/10/2016. 19 | * 20 | */ 21 | 22 | val defaultClient = OkHttpClient() 23 | 24 | fun Response.getKotlinHeaders(): List> { 25 | val headers = headers() 26 | val list = mutableListOf>() 27 | for (i in 0..headers.size() - 1) { 28 | list += headers.name(i) to headers.value(i) 29 | } 30 | return list 31 | } 32 | 33 | fun T.gsonToRequestBody(gson: Gson = MyGson.gson): RequestBody = object : RequestBody() { 34 | override fun contentType(): MediaType = MediaTypes.JSON!! 35 | val string = this@gsonToRequestBody.gsonToString() 36 | val bytes = string.toByteArray() 37 | override fun contentLength(): Long = bytes.size.toLong() 38 | override fun writeTo(sink: BufferedSink) { 39 | sink.write(bytes) 40 | } 41 | 42 | override fun toString(): String = string 43 | } 44 | 45 | fun JsonElement.toRequestBody(): RequestBody = object : RequestBody() { 46 | override fun contentType(): MediaType = MediaTypes.JSON!! 47 | val string = this@toRequestBody.toString() 48 | val bytes = string.toByteArray() 49 | override fun contentLength(): Long = bytes.size.toLong() 50 | override fun writeTo(sink: BufferedSink) { 51 | sink.write(bytes) 52 | } 53 | 54 | override fun toString(): String = string 55 | } 56 | 57 | fun String.toRequestBody(): RequestBody = object : RequestBody() { 58 | override fun contentType(): MediaType = MediaTypes.TEXT!! 59 | val bytes = this@toRequestBody.toByteArray() 60 | override fun contentLength(): Long = bytes.size.toLong() 61 | override fun writeTo(sink: BufferedSink) { 62 | sink.write(bytes) 63 | } 64 | 65 | override fun toString(): String = this@toRequestBody 66 | } 67 | 68 | fun ByteArray.toRequestBody(): RequestBody = object : RequestBody() { 69 | override fun contentType(): MediaType = MediaTypes.TEXT!! 70 | val bytes = this@toRequestBody 71 | override fun contentLength(): Long = bytes.size.toLong() 72 | override fun writeTo(sink: BufferedSink) { 73 | sink.write(bytes) 74 | } 75 | 76 | override fun toString(): String = "ByteArray" 77 | } 78 | 79 | fun File.toRequestBody(type: MediaType): RequestBody = object : RequestBody() { 80 | override fun contentLength(): Long = this@toRequestBody.length() 81 | override fun contentType(): MediaType = type 82 | override fun writeTo(sink: BufferedSink) { 83 | var source: Source? = null 84 | try { 85 | source = Okio.source(this@toRequestBody) 86 | sink.writeAll(source) 87 | } finally { 88 | Util.closeQuietly(source) 89 | } 90 | } 91 | 92 | override fun toString(): String = this@toRequestBody.toString() 93 | } 94 | 95 | inline fun Request.Builder.lambdaCustom(client: OkHttpClient = defaultClient, 96 | crossinline convert: (Response) -> TypedResponse 97 | ): () -> TypedResponse { 98 | val request = build() 99 | return { 100 | convert(client.newCall(request).execute()) 101 | } 102 | } 103 | 104 | inline fun Request.Builder.lambda(client: OkHttpClient = defaultClient, 105 | crossinline convert: (Response) -> T 106 | ): () -> TypedResponse { 107 | val request = build() 108 | return { 109 | try { 110 | val it = client.newCall(request).execute() 111 | if (it.isSuccessful) { 112 | val result = convert(it) 113 | TypedResponse(it.code(), result, it.getKotlinHeaders(), null, debugNetworkRequestInfo = request.getDebugInfoString()) 114 | } else { 115 | TypedResponse(it.code(), null, it.getKotlinHeaders(), it.body()!!.bytes(), debugNetworkRequestInfo = request.getDebugInfoString()) 116 | } 117 | } catch(e: Exception) { 118 | TypedResponse(0, null, listOf(), null, e, debugNetworkRequestInfo = request.getDebugInfoString()) 119 | } 120 | } 121 | } 122 | 123 | fun Request.getDebugInfoString(): String = "Request{method=${method()}, url=${url()}, tag=${if (tag() !== this) tag() else null}, headers=${headers()}, body=${body()}}" 124 | 125 | fun Request.Builder.lambdaUnit(client: OkHttpClient = defaultClient) = lambda(client) { Unit } 126 | 127 | fun Request.Builder.lambdaString(client: OkHttpClient = defaultClient) = lambda(client) { it.body()!!.string() } 128 | 129 | fun Request.Builder.lambdaBytes(client: OkHttpClient = defaultClient) = lambda(client) { it.body()!!.bytes() } 130 | 131 | fun Request.Builder.lambdaStream(client: OkHttpClient = defaultClient) = lambda(client) { it.body()!!.byteStream() } 132 | 133 | fun Request.Builder.lambdaJson(client: OkHttpClient = defaultClient) = lambda(client) { MyGson.json.parse(it.body()!!.string()) } 134 | 135 | fun Request.Builder.lambdaDownload(client: OkHttpClient = defaultClient, downloadFile: File) = lambda(client) { 136 | it.body()!!.byteStream().writeToFile(downloadFile) 137 | downloadFile 138 | } 139 | 140 | inline fun Request.Builder.lambdaGson(client: OkHttpClient = defaultClient) = lambda(client) { 141 | val str = it.body()!!.string() 142 | println(str) 143 | MyGson.gson.fromJson(str) 144 | } 145 | 146 | inline fun Request.Builder.lambdaGson(client: OkHttpClient = defaultClient, type: Type) = lambda(client) { 147 | val str = it.body()!!.string() 148 | println(str) 149 | MyGson.gson.fromJson(str, type) 150 | } -------------------------------------------------------------------------------- /src/main/java/com/lightningkite/kotlin/networking/TypedResponse.kt: -------------------------------------------------------------------------------- 1 | package com.lightningkite.kotlin.networking 2 | 3 | import com.google.gson.JsonElement 4 | import com.google.gson.JsonNull 5 | import com.lightningkite.kotlin.async.doUiThread 6 | 7 | /** 8 | * Created by josep on 11/10/2016. 9 | */ 10 | class TypedResponse( 11 | val code: Int = 0, 12 | val result: T? = null, 13 | val headers: List> = listOf(), 14 | val errorBytes: ByteArray? = null, 15 | val exception: Exception? = null, 16 | val debugNetworkRequestInfo: String? = null 17 | ) { 18 | fun isSuccessful(): Boolean = code / 100 == 2 19 | val errorString: String? get() = errorBytes?.toString(Charsets.UTF_8) ?: exception?.toString() 20 | val errorJson: JsonElement? get() = try { 21 | MyGson.json.parse(errorBytes?.toString(Charsets.UTF_8)) 22 | } catch(e: Exception) { 23 | e.printStackTrace() 24 | JsonNull.INSTANCE 25 | } 26 | 27 | override fun toString(): String { 28 | return "$code: result = $result, error = $errorString, requestInfo = $debugNetworkRequestInfo" 29 | } 30 | 31 | fun copy(code: Int, errorString: String?): TypedResponse = TypedResponse(code, result, headers, errorString?.toByteArray(), exception, debugNetworkRequestInfo) 32 | fun copy(result: A? = null): TypedResponse = TypedResponse(code, result, headers, errorBytes, exception, debugNetworkRequestInfo) 33 | inline fun map(mapper: (T) -> A): TypedResponse = try{ 34 | TypedResponse(code, if (result != null) mapper(result) else null, headers, errorBytes, exception, debugNetworkRequestInfo) 35 | } catch(e:Exception){ 36 | TypedResponse(0, null, headers, errorBytes, e, debugNetworkRequestInfo) 37 | } 38 | } 39 | 40 | inline fun (() -> TypedResponse).captureSuccess(crossinline onSuccess: (T) -> Unit): () -> TypedResponse { 41 | return { 42 | val response = this.invoke() 43 | if (response.isSuccessful()) { 44 | doUiThread { onSuccess(response.result!!) } 45 | } 46 | response 47 | } 48 | } 49 | 50 | inline fun (() -> TypedResponse).captureFailure(crossinline onFailure: (TypedResponse) -> Unit): () -> TypedResponse { 51 | return { 52 | val response = this.invoke() 53 | if (!response.isSuccessful()) { 54 | doUiThread { onFailure(response) } 55 | } 56 | response 57 | } 58 | } 59 | 60 | inline fun (() -> TypedResponse).chain(crossinline otherLambdaGenerator: (A) -> () -> TypedResponse): () -> TypedResponse { 61 | return { 62 | val response = this.invoke() 63 | if (!response.isSuccessful()) { 64 | TypedResponse(response.code, null, response.headers, response.errorBytes, response.exception) 65 | } else { 66 | otherLambdaGenerator(response.result!!).invoke() 67 | } 68 | } 69 | } 70 | 71 | inline fun (() -> TypedResponse).chainTypeless(crossinline default: (TypedResponse) -> B, crossinline otherLambdaGenerator: (A) -> () -> B): () -> B { 72 | return { 73 | val response = this.invoke() 74 | if (!response.isSuccessful()) { 75 | default(response) 76 | } else { 77 | otherLambdaGenerator(response.result!!).invoke() 78 | } 79 | } 80 | } 81 | 82 | inline fun (() -> TypedResponse).mapResult(crossinline mapper: (A) -> B): () -> TypedResponse { 83 | return { 84 | try { 85 | this.invoke().map(mapper) 86 | } catch (e: Exception) { 87 | TypedResponse(1, null, exception = e) 88 | } 89 | } 90 | } --------------------------------------------------------------------------------