diff --git a/CHANGELOG.md b/CHANGELOG.md index 662d08b..ab8a29c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ # CHANGELOG +## Version 0.12.0 (2017-08-04) + +- [kotlinx.coroutines 0.24.0](https://github.com/Kotlin/kotlinx.coroutines/releases/tag/0.24.0) +- Compiled against Kotlin 1.2.60 + ## Version 0.11.0 (2017-06-12) -- [kotlinx.coroutines 0.23.1](https://github.com/Kotlin/kotlinx.coroutines/releases/) +- [kotlinx.coroutines 0.23.1](https://github.com/Kotlin/kotlinx.coroutines/releases/tag/0.23.1) - Compiled against Kotlin 1.2.41 Thanks to [Thomas Schmidt](https://github.com/bohsen) for contribution diff --git a/README.md b/README.md index c021439..2db28b0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,11 @@ This is a small library that provides the [Kotlin Coroutines](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md) [suspending](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#suspending-functions) extension `Call.await()` for [Retrofit 2](https://github.com/square/retrofit) -Based on [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) implementation +Based on [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) implementation. + +This branch uses Kotlin experimental package `kotlin.coroutines.experimental` (pre-1.3). + +Migration to package stable `kotlin.coroutines` package is planned and work in progress. ## Download Download the [JAR](https://bintray.com/gildor/maven/kotlin-coroutines-retrofit#files/ru/gildor/coroutines/kotlin-coroutines-retrofit): @@ -14,16 +18,16 @@ Download the [JAR](https://bintray.com/gildor/maven/kotlin-coroutines-retrofit#f Gradle: ```groovy -compile 'ru.gildor.coroutines:kotlin-coroutines-retrofit:0.11.0' +compile 'ru.gildor.coroutines:kotlin-coroutines-retrofit:0.12.0' ``` -Maven: +Maven:getOrThrow ```xml ru.gildor.coroutines kotlin-coroutines-retrofit - 0.11.0 + 0.12.0 ``` @@ -49,7 +53,7 @@ In case of an HTTP error or an invocation exception `await()` throws an exceptio ```kotlin // You can use retrofit suspended extension inside any coroutine block -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { try { // Wait (suspend) for result val user: User = api.getUser("username").await() @@ -76,7 +80,7 @@ In case of an invocation exception `awaitResponse()` throws an exception ```kotlin // You can use retrofit suspended extension inside any coroutine block -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { try { // Wait (suspend) for response val response: Response = api.getUser("username").awaitResponse() @@ -100,7 +104,7 @@ fun Call.awaitResult(): Result ``` ```kotlin -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { // Wait (suspend) for Result val result: Result = api.getUser("username").awaitResult() // Check result type @@ -118,7 +122,7 @@ fun main(args: Array) = runBlocking { Also, `Result` has a few handy extension functions that allow to avoid `when` block matching: ```kotlin -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { val result: User = api.getUser("username").awaitResult() //Return value for success or null for any http error or exception @@ -138,7 +142,7 @@ All `Result` classes also implemented one or both interfaces: `ResponseResult` a You can use them for access to shared properties of different classes from `Result` ```kotlin -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { val result: User = api.getUser("username").awaitResult() //Result.Ok and Result.Error both implement ResponseResult @@ -162,7 +166,7 @@ extensions `.await()` and `.awaitResult()` are available only for non-nullable `Call` or platform `Call` body types: ```kotlin -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { val user: Call = api.getUser("username") val userOrNull: Call = api.getUserOrNull("username") @@ -194,7 +198,7 @@ You can do that by wrapping calls with `kotlinx.coroutines` [async()](https://ko ```kotlin -fun main(args: Array) = runBlocking { +fun main(args: Array): Unit = runBlocking { val users = listOf("user1", "user2", "user3") .map { username -> // Pass any coroutine context that fits better for your case diff --git a/build.gradle.kts b/build.gradle.kts index 14e1574..f09f352 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,15 +12,15 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper plugins { - id("org.jetbrains.kotlin.jvm") version "1.2.41" - id("com.jfrog.bintray") version "1.7.3" + id("org.jetbrains.kotlin.jvm") version "1.2.60" + id("com.jfrog.bintray") version "1.8.4" jacoco `maven-publish` id("org.jetbrains.dokka") version "0.9.16" } group = "ru.gildor.coroutines" -version = "0.11.0" +version = "0.12.0" description = "Provides Kotlin Coroutines suspendable await() extensions for Retrofit Call" repositories { @@ -34,7 +34,7 @@ java { dependencies { compile("org.jetbrains.kotlin:kotlin-stdlib") - compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.1") + compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.24.0") compile("com.squareup.retrofit2:retrofit:2.4.0") testCompile("junit:junit:4.12") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 736fb7d..91ca28c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 73bb13d..6d2395a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +# suppress inspection "UnusedProperty" for whole file distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip diff --git a/src/main/kotlin/ru/gildor/coroutines/retrofit/CallAwait.kt b/src/main/kotlin/ru/gildor/coroutines/retrofit/CallAwait.kt index 5b69a38..1d9d95b 100644 --- a/src/main/kotlin/ru/gildor/coroutines/retrofit/CallAwait.kt +++ b/src/main/kotlin/ru/gildor/coroutines/retrofit/CallAwait.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Andrey Mischenko + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ru.gildor.coroutines.retrofit import kotlinx.coroutines.experimental.CancellableContinuation @@ -8,7 +24,7 @@ import retrofit2.HttpException import retrofit2.Response /** - * Suspend extension that allows suspend [Call] inside coroutine. + * Suspend extension that allows suspend [Call] inside of a coroutine. * * @return Result of request or throw exception */ @@ -20,7 +36,7 @@ public suspend fun Call.await(): T { val body = response.body() if (body == null) { continuation.resumeWithException( - NullPointerException("Response body is null: $response") + NullPointerException("Response body is null: $response") ) } else { continuation.resume(body) @@ -68,23 +84,24 @@ public suspend fun Call.awaitResponse(): Response { * Suspend extension that allows suspend [Call] inside coroutine. * * @return sealed class [Result] object that can be - * casted to [Result.Ok] (success) or [Result.Error] (HTTP error) and [Result.Exception] (other errors) + * casted to [Result.Ok] (success) or [Result.Error] (HTTP error) + * and [Result.Exception] (other errors) */ public suspend fun Call.awaitResult(): Result { return suspendCancellableCoroutine { continuation -> enqueue(object : Callback { override fun onResponse(call: Call?, response: Response) { continuation.resume( - if (response.isSuccessful) { - val body = response.body() - if (body == null) { - Result.Exception(NullPointerException("Response body is null")) - } else { - Result.Ok(body, response.raw()) - } + if (response.isSuccessful) { + val body = response.body() + if (body == null) { + Result.Exception(NullPointerException("Response body is null")) } else { - Result.Error(HttpException(response), response.raw()) + Result.Ok(body, response.raw()) } + } else { + Result.Error(HttpException(response), response.raw()) + } ) } diff --git a/src/main/kotlin/ru/gildor/coroutines/retrofit/Result.kt b/src/main/kotlin/ru/gildor/coroutines/retrofit/Result.kt index 4298e8a..770f410 100644 --- a/src/main/kotlin/ru/gildor/coroutines/retrofit/Result.kt +++ b/src/main/kotlin/ru/gildor/coroutines/retrofit/Result.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Andrey Mischenko + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ru.gildor.coroutines.retrofit import okhttp3.Response @@ -12,8 +28,8 @@ public sealed class Result { * Successful result of request without errors */ public class Ok( - public val value: T, - override val response: Response + public val value: T, + override val response: Response ) : Result(), ResponseResult { override fun toString() = "Result.Ok{value=$value, response=$response}" } @@ -22,8 +38,8 @@ public sealed class Result { * HTTP error */ public class Error( - override val exception: HttpException, - override val response: Response + override val exception: HttpException, + override val response: Response ) : Result(), ErrorResult, ResponseResult { override fun toString() = "Result.Error{exception=$exception}" } @@ -33,7 +49,7 @@ public sealed class Result { * exception occurred creating the request or processing the response */ public class Exception( - override val exception: Throwable + override val exception: Throwable ) : Result(), ErrorResult { override fun toString() = "Result.Exception{$exception}" } @@ -57,14 +73,12 @@ public interface ErrorResult { /** * Returns [Result.Ok.value] or `null` */ -public fun Result.getOrNull(): T? = - if (this is Result.Ok) this.value else null +public fun Result.getOrNull(): T? = (this as? Result.Ok)?.value /** * Returns [Result.Ok.value] or [default] */ -public fun Result.getOrDefault(default: T): T = - getOrNull() ?: default +public fun Result.getOrDefault(default: T): T = getOrNull() ?: default /** * Returns [Result.Ok.value] or throw [throwable] or [ErrorResult.exception] diff --git a/src/test/kotlin/ru/gildor/coroutines/retrofit/CallAwaitTest.kt b/src/test/kotlin/ru/gildor/coroutines/retrofit/CallAwaitTest.kt index d78a527..ba0015b 100644 --- a/src/test/kotlin/ru/gildor/coroutines/retrofit/CallAwaitTest.kt +++ b/src/test/kotlin/ru/gildor/coroutines/retrofit/CallAwaitTest.kt @@ -1,10 +1,31 @@ +/* + * Copyright 2018 Andrey Mischenko + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ru.gildor.coroutines.retrofit import kotlinx.coroutines.experimental.CoroutineScope import kotlinx.coroutines.experimental.Unconfined import kotlinx.coroutines.experimental.async import kotlinx.coroutines.experimental.runBlocking -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.Test import retrofit2.Call import retrofit2.HttpException @@ -232,12 +253,12 @@ class CallAwaitTest { } private fun checkRequestCancelWithException( - block: suspend (Call) -> T + block: suspend (Call) -> T ) = testBlocking { val request = MockedCall( - ok = DONE, - autoStart = false, - cancelException = IllegalStateException() + ok = DONE, + autoStart = false, + cancelException = IllegalStateException() ) val async = async(coroutineContext, block = { block(request) }) //We shouldn't crash on cancel exception @@ -252,8 +273,8 @@ class CallAwaitTest { private fun checkJobCancelWithException(block: suspend (Call) -> T) = testBlocking { val request = MockedCall( - exception = IllegalArgumentException(), - autoStart = false + exception = IllegalArgumentException(), + autoStart = false ) val result = async(coroutineContext) { block(request) @@ -264,7 +285,7 @@ class CallAwaitTest { } private fun checkJobCancel( - block: suspend (Call) -> T + block: suspend (Call) -> T ) = testBlocking { val request = MockedCall(DONE, autoStart = false) val async = async(coroutineContext) { block(request) } @@ -277,3 +298,4 @@ class CallAwaitTest { private fun testBlocking(block: suspend CoroutineScope.() -> Unit) { runBlocking(Unconfined, block) } + diff --git a/src/test/kotlin/ru/gildor/coroutines/retrofit/ResultTest.kt b/src/test/kotlin/ru/gildor/coroutines/retrofit/ResultTest.kt index efc74ea..cb49699 100644 --- a/src/test/kotlin/ru/gildor/coroutines/retrofit/ResultTest.kt +++ b/src/test/kotlin/ru/gildor/coroutines/retrofit/ResultTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Andrey Mischenko + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ru.gildor.coroutines.retrofit import org.junit.Assert.assertEquals @@ -55,24 +71,24 @@ class ResultTest { @Test fun okToString() { assertEquals( - "Result.Ok{value=result, response=Response{protocol=http/1.1, code=200, message=mock response, url=http://localhost/}}", - ok.toString() + "Result.Ok{value=result, response=Response{protocol=http/1.1, code=200, message=mock response, url=http://localhost/}}", + ok.toString() ) } @Test fun errorToString() { assertEquals( - "Result.Error{exception=retrofit2.HttpException: HTTP 400 Response.error()}", - error.toString() + "Result.Error{exception=retrofit2.HttpException: HTTP 400 Response.error()}", + error.toString() ) } @Test fun exceptionToString() { assertEquals( - "Result.Exception{java.lang.IllegalArgumentException: Exception message}", - exception.toString() + "Result.Exception{java.lang.IllegalArgumentException: Exception message}", + exception.toString() ) } } \ No newline at end of file diff --git a/src/test/kotlin/ru/gildor/coroutines/retrofit/util/MockedCall.kt b/src/test/kotlin/ru/gildor/coroutines/retrofit/util/MockedCall.kt index b5e4286..c456412 100644 --- a/src/test/kotlin/ru/gildor/coroutines/retrofit/util/MockedCall.kt +++ b/src/test/kotlin/ru/gildor/coroutines/retrofit/util/MockedCall.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018 Andrey Mischenko + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ru.gildor.coroutines.retrofit.util import okhttp3.MediaType @@ -10,11 +26,11 @@ import retrofit2.HttpException import retrofit2.Response class MockedCall( - private val ok: T? = null, - private val error: HttpException? = null, - private val exception: Throwable? = null, - private val autoStart: Boolean = true, - private val cancelException: Throwable? = null + private val ok: T? = null, + private val error: HttpException? = null, + private val exception: Throwable? = null, + private val autoStart: Boolean = true, + private val cancelException: Throwable? = null ) : Call { private var executed: Boolean = false private var cancelled: Boolean = false @@ -62,12 +78,16 @@ class MockedCall( } -fun errorResponse(code: Int = 400, message: String = "Error response $code"): Response = - Response.error(code, ResponseBody.create(MediaType.parse("text/plain"), message)) +fun errorResponse( + code: Int = 400, + message: String = "Error response $code" +): Response { + return Response.error(code, ResponseBody.create(MediaType.parse("text/plain"), message)) +} fun okHttpResponse(code: Int = 200): okhttp3.Response = okhttp3.Response.Builder() - .code(code) - .protocol(Protocol.HTTP_1_1) - .message("mock response") - .request(Request.Builder().url("http://localhost").build()) - .build() \ No newline at end of file + .code(code) + .protocol(Protocol.HTTP_1_1) + .message("mock response") + .request(Request.Builder().url("http://localhost").build()) + .build() \ No newline at end of file diff --git a/src/test/kotlin/ru/gildor/coroutines/retrofit/util/NullBodyCall.kt b/src/test/kotlin/ru/gildor/coroutines/retrofit/util/NullBodyCall.kt index 97b1828..10ea485 100644 --- a/src/test/kotlin/ru/gildor/coroutines/retrofit/util/NullBodyCall.kt +++ b/src/test/kotlin/ru/gildor/coroutines/retrofit/util/NullBodyCall.kt @@ -1,11 +1,11 @@ /* - * Copyright 2016-2017 JetBrains s.r.o. + * Copyright 2018 Andrey Mischenko * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS,