Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling Exception in HttpClient Ktor

I have written a common code in a common module as below and tested in JS environment

val response = client.post<HttpResponse>(url) {
    body = TextContent("""{"a":1,"b":2}""", ContentType.Application.Json)
}
if (response.status != HttpStatusCode.OK) {
    logger.error("Error, this one failed bad?")
}

But my code ends at client.post with a canceled corutineException on no network. How do I handle this and any other exception? If there is an internet connection. Nothing fails, I want to be able to handle the exceptions. How?

Note: try, catch doesn't work

like image 317
andylamax Avatar asked Sep 13 '25 16:09

andylamax


1 Answers

We can handle it in a central way using this extension function that wraps Success and error cases. We can have Network Error, Serialisation Error or Server Errors which may expose error bodies/messages for the UI and http status code.

suspend inline fun <reified T, reified E> HttpClient.safeRequest(
    block: HttpRequestBuilder.() -> Unit,
): ApiResponse<T, E> =
    try {
        val response = request { block() }
        ApiResponse.Success(response.body())
    } catch (e: ClientRequestException) {
        ApiResponse.Error.HttpError(e.response.status.value, e.errorBody())
    } catch (e: ServerResponseException) {
        ApiResponse.Error.HttpError(e.response.status.value, e.errorBody())
    } catch (e: IOException) {
        ApiResponse.Error.NetworkError
    } catch (e: SerializationException) {
        ApiResponse.Error.SerializationError
    }

suspend inline fun <reified E> ResponseException.errorBody(): E? =
    try {
        response.body()
    } catch (e: SerializationException) {
        null
    }

Here's the ApiResponse defined below so there's working code:

sealed class ApiResponse<out T, out E> {
    /**
     * Represents successful network responses (2xx).
     */
    data class Success<T>(val body: T) : ApiResponse<T, Nothing>()

    sealed class Error<E> : ApiResponse<Nothing, E>() {
        /**
         * Represents server (50x) and client (40x) errors.
         */
        data class HttpError<E>(val code: Int, val errorBody: E?) : Error<E>()

        /**
         * Represent IOExceptions and connectivity issues.
         */
        object NetworkError : Error<Nothing>()

        /**
         * Represent SerializationExceptions.
         */
        object SerializationError : Error<Nothing>()
    }
}

I found ApiResponse works well for my case but you don't necessarily have to model your api responses as this if you have other/special cases, you can also use kotlin-result or Arrow's Either.

like image 94
Rahul Sainani Avatar answered Sep 15 '25 07:09

Rahul Sainani