diff --git a/README.md b/README.md index 0c29b9763..5a2c774b4 100644 --- a/README.md +++ b/README.md @@ -694,11 +694,11 @@ Fuel.request(WeatherApi.weatherFor("london")).responseJson { request, response, ### Coroutines Support -Coroutines module provides extension functions to wrap a response inside a coroutine and handle its result. The coroutines-based API provides equivalent methods to the standard API (e.g: `responseString()` in coroutines is `awaitString()`). +Coroutines module provides extension functions to wrap a response inside a coroutine and handle its result. The coroutines-based API provides equivalent methods to the standard API (e.g: `awaitStringResponse()` in coroutines is `awaitString()`). ```kotlin runBlocking { - val (request, response, result) = Fuel.get("https://httpbin.org/ip").awaitString() +val (request, response, result) = Fuel.get("https://httpbin.org/ip").awaitStringResult() result.fold({ data -> println(data) // "{"origin":"127.0.0.1"}" @@ -708,19 +708,19 @@ runBlocking { } ``` -It also provides useful methods to handle the `Result` value directly. The difference is that they throw exception instead of returning it wrapped a `FuelError` instance. +It also provides useful methods to retrieve the `ByteArray`,`String` or `Object`. The difference with these implementations is that they throw exception instead of returning it wrapped a `FuelError` instance. ```kotlin runBlocking { try { - println(Fuel.get("https://httpbin.org/ip").awaitStringResult()) // "{"origin":"127.0.0.1"}" - } catch(exception: HttpException) { + println(Fuel.get("https://httpbin.org/ip").awaitString()) // "{"origin":"127.0.0.1"}" + } catch(exception: Exception) { println("A network request exception was thrown: ${exception.message}") } } ``` -Handling objects other than `String` (`awaitString()`) or `ByteArray` (`awaitResponse()`) can be done using `awaitSafelyObjectResult` or `awaitObjectResult`. +Handling objects other than `String` (`awaitString()`) or `ByteArray` (`awaitByteArrayResponse()`) can be done using `awaitObjectResult` ```kotlin data class Ip(val origin: String) @@ -733,7 +733,7 @@ object IpDeserializer : ResponseDeserializable { ```kotlin runBlocking { - Fuel.get("https://httpbin.org/ip").awaitSafelyObjectResult(IpDeserializer) + Fuel.get("https://httpbin.org/ip").awaitObjectResult(IpDeserializer) .fold({ data -> println(data.origin) // 127.0.0.1 }, { error -> @@ -745,12 +745,13 @@ runBlocking { ```kotlin runBlocking { try { - val data = Fuel.get("https://httpbin.org/ip").awaitObjectResult(IpDeserializer) + val data = Fuel.get("https://httpbin.org/ip").awaitObject(IpDeserializer) println(data.origin) // 127.0.0.1 - } catch (exception: HttpException) { - println("A network request exception was thrown: ${exception.message}") - } catch (exception: JsonMappingException) { - println("A serialization/deserialization exception was thrown: ${exception.message}") + } catch (exception: Exception) { + when (exception){ + is HttpException -> println("A network request exception was thrown: ${exception.message}") + is JsonMappingException -> println("A serialization/deserialization exception was thrown: ${exception.message}") + else -> println("An error [${exception.javaClass.simpleName}\"] was thrown") } } ``` diff --git a/fuel-coroutines/src/main/kotlin/com/github/kittinunf/fuel/coroutines/Coroutines.kt b/fuel-coroutines/src/main/kotlin/com/github/kittinunf/fuel/coroutines/Coroutines.kt index ef48ffd80..9b209bbb5 100644 --- a/fuel-coroutines/src/main/kotlin/com/github/kittinunf/fuel/coroutines/Coroutines.kt +++ b/fuel-coroutines/src/main/kotlin/com/github/kittinunf/fuel/coroutines/Coroutines.kt @@ -1,3 +1,4 @@ + import com.github.kittinunf.fuel.core.Deserializable import com.github.kittinunf.fuel.core.FuelError import com.github.kittinunf.fuel.core.Request @@ -7,7 +8,6 @@ import com.github.kittinunf.fuel.core.Response import com.github.kittinunf.fuel.core.ResponseDeserializable import com.github.kittinunf.fuel.core.response import com.github.kittinunf.result.Result -import com.github.kittinunf.result.mapError import kotlinx.coroutines.experimental.suspendCancellableCoroutine import java.nio.charset.Charset @@ -19,49 +19,68 @@ private suspend fun > Request.await( continuation.resume(response(deserializable)) } -suspend fun Request.awaitResponse(): Triple> = + +suspend fun Request.awaitByteArrayResponse(): Triple> = await(byteArrayDeserializer()) -suspend fun Request.awaitString( +suspend fun Request.awaitStringResponse( charset: Charset = Charsets.UTF_8 ): Triple> = await(stringDeserializer(charset)) -@Deprecated( - replaceWith = ReplaceWith( - expression = ".awaitSafelyObjectResult"), - level = DeprecationLevel.WARNING, - message = "This function cannot handle exceptions properly which causes API inconsistency.") -@Throws -suspend fun Request.awaitObject( +suspend fun Request.awaitObjectResponse( deserializable: ResponseDeserializable ): Triple> = await(deserializable) +/*** + * + * Response functions all these return a Type + * + * @return ByteArray if no exceptions are thrown + */ @Throws -suspend fun Request.awaitResponseResult(): ByteArray = awaitResponse().third - .mapError { throw it.exception } - .get() +suspend fun Request.awaitByteArray(): ByteArray = await(byteArrayDeserializer()).third.get() +/** + * @note errors thrown in deserialization will not be caught + * + * @return ByteArray if no exceptions are thrown + */ @Throws -suspend fun Request.awaitStringResult( +suspend fun Request.awaitString( charset: Charset = Charsets.UTF_8 -): String = awaitString(charset).third - .mapError { throw it.exception } - .get() +): String = await(stringDeserializer(charset)).third.get() /** - * This function will throw the an exception if an error is thrown either at the HTTP level + * @note This function will throw the an exception if an error is thrown either at the HTTP level * or during deserialization * * @param deserializable * * @return Result object - * */ + */ @Throws -suspend fun Request.awaitObjectResult( +suspend fun Request.awaitObject( deserializable: ResponseDeserializable -): U = await(deserializable).third - .mapError { throw it.exception } - .get() +): U = await(deserializable).third.get() + +/*** + * + * Response functions all these return a Result + * + * @return Result + */ +suspend fun Request.awaitByteArrayResult(): Result = awaitByteArrayResponse().third + +/** + * + * @param charset this is defaults to UTF-8 + * + * @return Result + */ +suspend fun Request.awaitStringResult( + charset: Charset = Charsets.UTF_8 +): Result = awaitStringResponse(charset).third + /** * This function catches both server errors and Deserialization Errors @@ -69,8 +88,8 @@ suspend fun Request.awaitObjectResult( * @param deserializable * * @return Result object - * */ -suspend fun Request.awaitSafelyObjectResult( + */ +suspend fun Request.awaitObjectResult( deserializable: ResponseDeserializable ): Result = try { await(deserializable).third @@ -81,3 +100,14 @@ suspend fun Request.awaitSafelyObjectResult( } Result.Failure(fuelError) } + +@Deprecated("please use 'awaitByteArray()'", ReplaceWith("awaitByteArray()", "deserializable")) +suspend fun Request.awaitResponseResult(): ByteArray = awaitByteArray() + +@Deprecated("please use 'awaitObjectResult(deserializable)'", ReplaceWith("awaitObjectResult(deserializable)")) +suspend fun Request.awaitSafelyObjectResult( + deserializable: ResponseDeserializable +): Result = this.awaitObjectResult(deserializable) + + + diff --git a/fuel-coroutines/src/test/kotlin/com/github/kittinunf/fuel/coroutines/CoroutinesTest.kt b/fuel-coroutines/src/test/kotlin/com/github/kittinunf/fuel/coroutines/CoroutinesTest.kt index f63170e8e..f1440b1a5 100644 --- a/fuel-coroutines/src/test/kotlin/com/github/kittinunf/fuel/coroutines/CoroutinesTest.kt +++ b/fuel-coroutines/src/test/kotlin/com/github/kittinunf/fuel/coroutines/CoroutinesTest.kt @@ -15,225 +15,200 @@ class CoroutinesTest { init { FuelManager.instance.basePath = "https://httpbin.org" - Fuel.testMode { timeout = 30000 } } @Test - fun testAwaitStringSuccess() = runBlocking { + fun testAwaitResponseSuccess() = runBlocking { try { - Fuel.get("/uuid").awaitString().third - .fold({ data -> - assertTrue(data.isNotEmpty()) - assertTrue(data.contains("uuid")) - }, { error -> - fail("This test should pass but got an error: ${error.message}") - }) + Fuel.get("/ip").awaitByteArrayResponse().third.fold({ data -> + assertTrue(data.isNotEmpty()) + }, { error -> + fail("This test should pass but got an error: ${error.message}") + }) } catch (exception: Exception) { - fail("When using awaitString errors should be folded instead of thrown.") + fail("When using awaitByteArrayResponse errors should be folded instead of thrown.") } } @Test - fun testAwaitStringErrorDueToNetwork() = runBlocking { + fun testAwaitResponseErrorDueToNetwork() = runBlocking { try { - Fuel.get("/not/found/address").awaitString().third.fold({ + Fuel.get("/invalid/url").awaitByteArrayResponse().third.fold({ fail("This test should fail due to HTTP status code.") }, { error -> assertTrue(error.exception is HttpException) - assertTrue(error.message.orEmpty().contains("HTTP Exception 404")) + assertTrue(error.message!!.contains("HTTP Exception 404")) }) } catch (exception: HttpException) { - fail("When using awaitString errors should be folded instead of thrown.") + fail("When using awaitByteArrayResponse errors should be folded instead of thrown.") } } @Test - fun testAwaitResponseSuccess() = runBlocking { + fun testAwaitStringResponseSuccess() = runBlocking { try { - Fuel.get("/ip").awaitResponse().third - .fold({ data -> - assertTrue(data.isNotEmpty()) - }, { error -> - fail("This test should pass but got an error: ${error.message}") - }) + Fuel.get("/uuid").awaitStringResponse().third.fold({ data -> + assertTrue(data.isNotEmpty()) + assertTrue(data.contains("uuid")) + }, { error -> + fail("This test should pass but got an error: ${error.message}") + }) } catch (exception: Exception) { - fail("When using awaitResponse errors should be folded instead of thrown.") + fail("When using awaitString errors should be folded instead of thrown.") } } @Test - fun testAwaitResponseErrorDueToNetwork() = runBlocking { - try { - Fuel.get("/invalid/url").awaitResponse().third.fold({ - fail("This test should fail due to HTTP status code.") - }, { error -> - assertTrue(error.exception is HttpException) - assertTrue(error.message!!.contains("HTTP Exception 404")) - }) - } catch (exception: HttpException) { - fail("When using awaitResponse errors should be folded instead of thrown.") - } + fun testAwaitObjectResponse() = runBlocking { + Fuel.get("/uuid").awaitObjectResponse(UUIDResponseDeserializer).third.fold({ data -> + assertTrue(data.uuid.isNotEmpty()) + }, { error -> + fail("This test should pass but got an error: ${error.message}") + }) } - private data class UUIDResponse(val uuid: String) + @Test + fun testAwaitStringResponseDoesNotThrowException() = runBlocking { + try { + Fuel.get("/not/found/address").awaitStringResponse().third.fold({ + fail("This should not be called") + }, { - private object UUIDResponseDeserializer : ResponseDeserializable { - override fun deserialize(content: String) = - jacksonObjectMapper().readValue(content) + }) + } catch (exception: Exception) { + fail("This test should fail as exception should be caught") + } } @Test - fun testAwaitObjectSuccess() = runBlocking { - try { - Fuel.get("/uuid").awaitObject(UUIDResponseDeserializer).third - .fold({ data -> - assertTrue(data.uuid.isNotEmpty()) - }, { error -> - fail("This test should pass but got an error: ${error.message}") - }) - } catch (exception: HttpException) { - fail("When using awaitObject network errors should be folded instead of thrown.") - } + fun testAwaitForByteArrayResult() = runBlocking { + Fuel.get("/ip").awaitByteArrayResult().fold({ data -> + assertTrue(data.isNotEmpty()) + }, { error -> + fail("This test should pass but got an error: ${error.message}") + }) } @Test - fun testAwaitObjectErrorDueToNetwork() = runBlocking { + fun testAwaitStringResultErrorDueToNetwork() = runBlocking { try { - Fuel.get("/not/uuid/endpoint").awaitObject(UUIDResponseDeserializer).third.fold({ + Fuel.get("/not/found/address").awaitStringResult().fold({ fail("This test should fail due to HTTP status code.") }, { error -> assertTrue(error.exception is HttpException) - assertTrue(error.message!!.contains("HTTP Exception 404")) + assertTrue(error.message.orEmpty().contains("HTTP Exception 404")) }) } catch (exception: HttpException) { - fail("When using awaitObject errors should be folded instead of thrown.") + fail("When using awaitString errors should be folded instead of thrown.") } } - private data class UUIDIntResponse(val uuid: Int) - - private object UUIDIntResponseDeserializer : ResponseDeserializable { - override fun deserialize(content: String) = - jacksonObjectMapper().readValue(content) + @Test + fun testItCanAwaitStringResult() = runBlocking { + Fuel.get("/uuid").awaitStringResult().fold({ data -> + assertTrue(data.isNotEmpty()) + assertTrue(data.contains("uuid")) + }, { error -> + fail("This test should pass but got an error: ${error.message}") + }) } @Test - fun testAwaitObjectDueToDeserialization() = runBlocking { + fun testAwaitForObjectResultCatchesError() = runBlocking { try { - Fuel.get("/uuid").awaitObject(UUIDIntResponseDeserializer).third.fold({ - fail("This test should fail because uuid property should be a String.") - }, { - fail("When using awaitObject serialization/deserialization errors are thrown.") + Fuel.get("/error/404").awaitObjectResult(UUIDResponseDeserializer).fold({ _ -> + fail("This is an error case!") + }, { error -> + assertTrue(error.exception is HttpException) }) - } catch (exception: JsonMappingException) { - assertNotNull(exception) + } catch (exception: Exception) { + fail("When using awaitSafelyObjectResult errors should be folded instead of thrown.") } } @Test - fun testAwaitStringResultSuccess() = runBlocking { + fun testAwaitForObjectResultCatchesDeserializeError() = runBlocking { try { - val data = Fuel.get("/uuid").awaitStringResult() - assertTrue(data.contains("uuid")) + Fuel.get("/ip").awaitObjectResult(UUIDResponseDeserializer).fold({ _ -> + fail("This is an error case!") + + }, { error -> + assertNotNull(error) + assertTrue(error.exception is JsonMappingException) + }) } catch (exception: Exception) { - fail("This test should pass but got an exception: ${exception.message}") + fail("When using awaitSafelyObjectResult errors should be folded instead of thrown.") } } @Test - fun testAwaitResponseResultSuccess() = runBlocking { - try { - val data = Fuel.get("/uuid").awaitResponseResult() - assertTrue(data.isNotEmpty()) - } catch (exception: Exception) { - fail("This test should pass but got an exception: ${exception.message}") - } + fun testItCanAwaitByteArray() = runBlocking { + assertTrue(Fuel.get("/uuid").awaitByteArray().isNotEmpty()) } @Test - fun testAwaitObjectResultSuccess() = runBlocking { + fun testAwaitResponseResultSuccess() = runBlocking { try { - val data = Fuel.get("/uuid").awaitObjectResult(UUIDResponseDeserializer) - assertTrue(data.uuid.isNotEmpty()) + val data = Fuel.get("/uuid").awaitByteArray() + assertTrue(data.isNotEmpty()) } catch (exception: Exception) { fail("This test should pass but got an exception: ${exception.message}") } } @Test - fun testAwaitObjectResultExceptionDueToNetwork() = runBlocking { + fun testItCanAwaitForStringResultCanThrowException() = runBlocking { try { - Fuel.get("/some/invalid/path").awaitObjectResult(UUIDResponseDeserializer) - fail("This test should raise an exception due to invalid URL") - } catch (exception: HttpException) { + Fuel.get("/error/404").awaitString() + fail("This test should fail due to status code 404") + } catch (exception: Exception) { assertNotNull(exception) - assertTrue(exception.message.orEmpty().contains("404")) } } @Test - fun testAwaitObjectResultExceptionDueToDeserialization() = runBlocking { + fun testAwaitStringResultSuccess() = runBlocking { try { - Fuel.get("/uuid").awaitObjectResult(UUIDIntResponseDeserializer) - fail("This test should fail because uuid property should be a String.") - } catch (exception: JsonMappingException) { - assertNotNull(exception) + val data = Fuel.get("/uuid").awaitString() + assertTrue(data.contains("uuid")) + } catch (exception: Exception) { + fail("This test should pass but got an exception: ${exception.message}") } } @Test - fun testItCanAwaitForStringResultCanThrowException() = runBlocking { - try { - Fuel.get("/error/404").awaitStringResult() - fail("This test should fail due to status code 404") - } catch (exception: HttpException) { - assertNotNull(exception) - } + fun testItCanAwaitForObject() = runBlocking { + assertTrue(Fuel.get("/uuid").awaitObject(UUIDResponseDeserializer).uuid.isNotEmpty()) } @Test - fun testAwaitSafelyObjectResultSuccess() = runBlocking { + fun testAwaitObjectResultSuccess() = runBlocking { try { - Fuel.get("/uuid").awaitSafelyObjectResult(UUIDResponseDeserializer) - .fold({ data -> - assertTrue(data.uuid.isNotEmpty()) - }, { error -> - fail("This test should pass but got an error: ${error.message}") - }) + val data = Fuel.get("/uuid").awaitObject(UUIDResponseDeserializer) + assertTrue(data.uuid.isNotEmpty()) } catch (exception: Exception) { - fail("When using awaitSafelyObjectResult errors should be folded instead of thrown.") + fail("This test should pass but got an exception: ${exception.message}") } } @Test - fun testAwaitSafelyObjectResultErrorDueToNetwork() = runBlocking { + fun testAwaitObjectResultExceptionDueToNetwork() = runBlocking { try { - Fuel.get("/error/404").awaitSafelyObjectResult(UUIDResponseDeserializer) - .fold({ - fail("This test should fail due to HTTP status code.") - }, { error -> - assertTrue(error.exception is HttpException) - }) + Fuel.get("/some/invalid/path").awaitObject(UUIDResponseDeserializer) + fail("This test should raise an exception due to invalid URL") } catch (exception: Exception) { - fail("When using awaitSafelyObjectResult errors should be folded instead of thrown.") + assertTrue(exception.message.orEmpty().contains("404")) } } - @Test - fun testAwaitSafelyObjectResultErrorDueToDeserialization() = runBlocking { - try { - Fuel.get("/ip").awaitSafelyObjectResult(UUIDResponseDeserializer) - .fold({ - fail("This test should fail due to HTTP status code.") - }, { error -> - assertNotNull(error) - assertTrue(error.exception is JsonMappingException) - }) - } catch (exception: Exception) { - fail("When using awaitSafelyObjectResult errors should be folded instead of thrown.") - } + private data class UUIDResponse(val uuid: String) + + private object UUIDResponseDeserializer : ResponseDeserializable { + override fun deserialize(content: String) = + jacksonObjectMapper().readValue(content) } } + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 427a607f5..1278271f9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Sat Jun 30 08:35:01 BST 2018 +#Wed Jul 04 23:16:01 BST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/sample-java/src/main/java/com/example/fuel/MainActivity.java b/sample-java/src/main/java/com/example/fuel/MainActivity.java index 3fcfaa432..cf77c1846 100644 --- a/sample-java/src/main/java/com/example/fuel/MainActivity.java +++ b/sample-java/src/main/java/com/example/fuel/MainActivity.java @@ -6,8 +6,6 @@ import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; -import android.widget.Button; -import android.widget.TextView; import com.example.fuel.databinding.ActivityMainBinding; import com.github.kittinunf.fuel.Fuel; diff --git a/sample/src/main/kotlin/com/example/fuel/MainActivity.kt b/sample/src/main/kotlin/com/example/fuel/MainActivity.kt index e88ab8e32..495bf85f0 100644 --- a/sample/src/main/kotlin/com/example/fuel/MainActivity.kt +++ b/sample/src/main/kotlin/com/example/fuel/MainActivity.kt @@ -5,6 +5,7 @@ import android.os.Handler import android.support.v7.app.AppCompatActivity import android.util.Log import awaitString +import awaitStringResponse import com.github.kittinunf.fuel.* import com.github.kittinunf.fuel.core.FuelError import com.github.kittinunf.fuel.core.FuelManager @@ -83,7 +84,7 @@ class MainActivity : AppCompatActivity() { } private suspend fun httpGetCoroutine() { - val (request, response, result) = Fuel.get("/get", listOf("userId" to "123")).awaitString() + val (request, response, result) = Fuel.get("/get", listOf("userId" to "123")).awaitStringResponse() Log.d(TAG, response.toString()) Log.d(TAG, request.toString()) update(result)