forked from Kaaveh/ComposeNews
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request Kaaveh#219 from mhmd-android/enhance/error-handling
Enhance/error handling
- Loading branch information
Showing
29 changed files
with
390 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
plugins { | ||
alias(libs.plugins.composenews.android.library) | ||
libs.plugins.apply { | ||
alias(kotlinx.serialization) | ||
} | ||
} | ||
|
||
android { | ||
namespace = "ir.composenews.ktor" | ||
|
||
} | ||
|
||
dependencies { | ||
libs.apply { | ||
implementation(bundles.ktor) | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
core/network/ktor/src/main/java/ir/composenews/network/ApiResponse.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package ir.composenews.network | ||
|
||
sealed interface ApiResponse<out T> { | ||
data class Success<T>(val data: T) : ApiResponse<T> | ||
|
||
/** | ||
* There are two subtypes: [ApiResponse.Failure.Error] and [ApiResponse.Failure.Exception]. | ||
*/ | ||
sealed interface Failure<T> : ApiResponse<T> { | ||
|
||
/** | ||
* API communication conventions do not match or applications need to handle errors. | ||
* e.g., internal server error and etc... | ||
*/ | ||
data class Error(val payload: Any?) : Failure<Nothing> { | ||
val message = payload.toString() | ||
} | ||
|
||
/** | ||
* An unexpected exception occurs while creating requests or processing an response in the client side. | ||
* e.g., network connection error, timeout and etc... | ||
*/ | ||
data class Exception(val throwable: Throwable) : Failure<Nothing> { | ||
val message: String? = throwable.message | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
core/network/ktor/src/main/java/ir/composenews/network/ApiResponseExtension.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
|
||
package ir.composenews.network | ||
|
||
import io.ktor.client.call.body | ||
import io.ktor.client.statement.HttpResponse | ||
|
||
val successCode: IntRange = 200..299 | ||
|
||
fun HttpResponse.getStatusCode(): StatusCode { | ||
return StatusCode.entries.find { | ||
it.code == status.value | ||
} ?: StatusCode.Unknown | ||
} | ||
|
||
val ApiResponse.Failure.Error.payloadResponse: HttpResponse | ||
inline get() = (payload as? HttpResponse) ?: throw IllegalArgumentException() | ||
|
||
val ApiResponse.Failure.Error.statusCode: StatusCode | ||
inline get() = payloadResponse.getStatusCode() | ||
|
||
suspend inline fun <reified T> apiResponseOf( | ||
successCodeRange: IntRange = successCode, | ||
crossinline f: suspend () -> HttpResponse, | ||
): ApiResponse<T> = try { | ||
val response = f() | ||
if (response.status.value in successCodeRange) { | ||
ApiResponse.Success( | ||
data = response.body() ?: Unit as T, | ||
) | ||
} else { | ||
ApiResponse.Failure.Error(response) | ||
} | ||
} catch (ex: Exception) { | ||
ApiResponse.Failure.Exception(ex) | ||
} |
16 changes: 16 additions & 0 deletions
16
core/network/ktor/src/main/java/ir/composenews/network/ErrorMapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
package ir.composenews.network | ||
|
||
fun StatusCode.mapMessageStatusCode(): String { | ||
return when (this) { | ||
StatusCode.Unknown -> "Unknown" | ||
StatusCode.BadRequest -> "Bad Request" | ||
StatusCode.Forbidden -> "Forbidden" | ||
StatusCode.RequestTimeout -> "Request Timeout" | ||
StatusCode.TooManyRequests -> "Too many Requests" | ||
StatusCode.InternalServerError -> "Internal Server Error" | ||
StatusCode.ServiceUnavailable -> "Service Unavailable" | ||
StatusCode.AccessDenied -> "Access Denied" | ||
StatusCode.LimitRequest -> "Limit Request" | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
core/network/ktor/src/main/java/ir/composenews/network/Errors.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
package ir.composenews.network | ||
|
||
sealed class Errors { | ||
data class ApiError(val message: String?, val code: Int) : Errors() | ||
|
||
data class ExceptionError(val message: String?, val throwable: Throwable? = null) : Errors() | ||
} |
17 changes: 17 additions & 0 deletions
17
core/network/ktor/src/main/java/ir/composenews/network/HttpClientExtension.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package ir.composenews.network | ||
|
||
import io.ktor.client.HttpClient | ||
import io.ktor.client.request.HttpRequestBuilder | ||
import io.ktor.client.request.request | ||
import io.ktor.http.HttpMethod | ||
|
||
suspend inline fun <reified T> HttpClient.get( | ||
builder: HttpRequestBuilder, | ||
): ApiResponse<T> { | ||
builder.method = HttpMethod.Get | ||
return apiResponseOf { request(builder) } | ||
} | ||
|
||
suspend inline fun <reified T> HttpClient.get( | ||
block: HttpRequestBuilder.() -> Unit, | ||
): ApiResponse<T> = this.get(HttpRequestBuilder().apply(block)) |
6 changes: 6 additions & 0 deletions
6
core/network/ktor/src/main/java/ir/composenews/network/Resource.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package ir.composenews.network | ||
|
||
sealed class Resource<out T, out E> { | ||
data class Success<out T>(val data: T) : Resource<T, Nothing>() | ||
data class Error<out E>(val error: E) : Resource<Nothing, E>() | ||
} |
96 changes: 96 additions & 0 deletions
96
core/network/ktor/src/main/java/ir/composenews/network/ResponseTransformer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
|
||
package ir.composenews.network | ||
|
||
import kotlin.contracts.ExperimentalContracts | ||
import kotlin.contracts.InvocationKind | ||
import kotlin.contracts.contract | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
inline fun <T> ApiResponse<T>.onSuccess( | ||
crossinline onResult: ApiResponse.Success<T>.() -> Unit, | ||
): ApiResponse<T> { | ||
contract { callsInPlace(onResult, InvocationKind.AT_MOST_ONCE) } | ||
if (this is ApiResponse.Success) { | ||
onResult(this) | ||
} | ||
return this | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
suspend inline fun <T> ApiResponse<T>.suspendOnSuccess( | ||
crossinline onResult: suspend ApiResponse.Success<T>.() -> Unit, | ||
): ApiResponse<T> { | ||
contract { callsInPlace(onResult, InvocationKind.AT_MOST_ONCE) } | ||
if (this is ApiResponse.Success) { | ||
onResult(this) | ||
} | ||
return this | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
suspend inline fun <T, V> ApiResponse.Success<T>.suspendMap( | ||
crossinline mapper: suspend (ApiResponse.Success<T>) -> V, | ||
): V { | ||
contract { callsInPlace(mapper, InvocationKind.AT_MOST_ONCE) } | ||
return mapper(this) | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
suspend inline fun <T> ApiResponse<T>.suspendOnError( | ||
crossinline onResult: suspend ApiResponse.Failure.Error.() -> Unit, | ||
): ApiResponse<T> { | ||
contract { callsInPlace(onResult, InvocationKind.AT_MOST_ONCE) } | ||
if (this is ApiResponse.Failure.Error) { | ||
onResult(this) | ||
} | ||
return this | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
inline fun <T> ApiResponse<T>.onError( | ||
crossinline onResult: ApiResponse.Failure.Error.() -> Unit, | ||
): ApiResponse<T> { | ||
contract { callsInPlace(onResult, InvocationKind.AT_MOST_ONCE) } | ||
if (this is ApiResponse.Failure.Error) { | ||
onResult(this) | ||
} | ||
return this | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
suspend inline fun <V> ApiResponse.Failure.Error.suspendMap( | ||
crossinline mapper: suspend (ApiResponse.Failure.Error) -> V, | ||
): V { | ||
contract { callsInPlace(mapper, InvocationKind.AT_MOST_ONCE) } | ||
return mapper(this) | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
suspend inline fun <T> ApiResponse<T>.suspendOnException( | ||
crossinline onResult: suspend ApiResponse.Failure.Exception.() -> Unit, | ||
): ApiResponse<T> { | ||
contract { callsInPlace(onResult, InvocationKind.AT_MOST_ONCE) } | ||
if (this is ApiResponse.Failure.Exception) { | ||
onResult(this) | ||
} | ||
return this | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
inline fun <T> ApiResponse<T>.onException( | ||
crossinline onResult: ApiResponse.Failure.Exception.() -> Unit, | ||
): ApiResponse<T> { | ||
contract { callsInPlace(onResult, InvocationKind.AT_MOST_ONCE) } | ||
if (this is ApiResponse.Failure.Exception) { | ||
onResult(this) | ||
} | ||
return this | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
suspend inline fun <V> ApiResponse.Failure.Exception.suspendMap( | ||
crossinline mapper: suspend (ApiResponse.Failure.Exception) -> V, | ||
): V { | ||
contract { callsInPlace(mapper, InvocationKind.AT_MOST_ONCE) } | ||
return mapper(this) | ||
} |
17 changes: 17 additions & 0 deletions
17
core/network/ktor/src/main/java/ir/composenews/network/StatusCode.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
|
||
package ir.composenews.network | ||
|
||
/** | ||
* https://docs.coingecko.com/reference/common-errors-rate-limit | ||
*/ | ||
enum class StatusCode(val code: Int) { | ||
Unknown(0), | ||
BadRequest(400), | ||
Forbidden(403), | ||
RequestTimeout(408), | ||
TooManyRequests(429), | ||
InternalServerError(500), | ||
ServiceUnavailable(500), | ||
AccessDenied(500), | ||
LimitRequest(10005), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.