Reputation: 4141
I have an api that returns error body with the correct error information when a bad request is sent. For eg I get status code 400 and the following body -
{
"errorCode": 1011,
"errorMessage": "Unable to get Child information"
}
Now when I am writing a ktor client in a multi-platform module for this, I catch this in a response validator like -
HttpResponseValidator {
validateResponse {
val statusCode = it.status.value
when (statusCode) {
in 300..399 -> print(it.content.toString())
in 400..499 -> {
print(it.content.toString())
throw ClientRequestException(it)
}
in 500..599 -> print(it.content.toString())
}
}
handleResponseException {
print(it.message)
}
}
My query here is I am not able to access the response error body in either validateResponse
or handleResponseException
. Is there a way i can catch and parse that to get the actual error sent by server?
Upvotes: 10
Views: 13737
Reputation: 3717
As others have pointed out you can declare the data class of your Error object, and since Ktor is already setup with the serialiser, we can get the response body from ResponseException
.
Here's an extension function for ease of use:
suspend inline fun <reified E> ResponseException.errorBody(): E? =
try {
response.body()
} catch (e: SerializationException) {
null
}
Upvotes: 0
Reputation: 1170
After spending hours, I got the error body with the below steps.
1. Define your model class for error. In my case it was something like
@Serializable
data class MyException(
val message : String,
val code : String,
val type : String,
val status_code : Int
) : RuntimeException()
You can see I've also extended custom class to RuntimeException, because I want my class behave like Exception class
2. Call the API
try {
val mClient = KtorClientFactory().build()
val res = mClient.post<MemberResponse>("${baseURL}user/login/") {
//.....
}
emit(DataState.Success(res))
} catch (ex: Exception) {
if (ex is ClientRequestException) {
val res = ex.response.readText(Charsets.UTF_8)
try {
val myException = Json { ignoreUnknownKeys = true }
.decodeFromString(MyException.serializer(), res)
emit(DataState.Error(myException))
} catch (ex: Exception) {
ex.printStackTrace()
}
} else
emit(DataState.Error(ex))
}
That's it. You've parsed the error body.
To understand it in short way, you simply need to focus in two steps.
1. val res = ex.response.readText(Charsets.UTF_8)
2. val myException = Json { ignoreUnknownKeys = true }.decodeFromString(MyException.serializer(), res)
Upvotes: 1
Reputation: 5955
Just in case this helps someone else searching in this space, in my ktor service design, the response.readText
made sense in my case:
try { httpClient.post... } catch(cre: ClientRequestException){ runBlocking { val content = cre.response?.readText(Charset.defaultCharset()) val cfResponse = Gson().fromJson(content, CfResponse::class.java) ... } }
Upvotes: 1
Reputation: 373
You can declare a data class Error to represent the error response you expect.
import kotlinx.serialization.Serializable
@Serializable
data class Error(
val errorCode: Int, //if by errorCode you mean the http status code is not really necessary to include here as you already know it from the validateResponse
val errorMessage: String
)
you can have a suspend fun to parse the response and have it as an instance of the Error data class
suspend fun getError(responseContent: ByteReadChannel): Error {
responseContent.readUTF8Line()?.let {
return Json(JsonConfiguration.Stable).parse(Error.serializer(), it)
}
throw IllegalArgumentException("not a parsable error")
}
then inside the handleResponseException
handleResponseException { cause ->
val error = when (cause) {
is ClientRequestException -> exceptionHandler.getError(cause.response.content)
// other cases here
else -> // throw Exception() do whatever you need
}
//resume with the error
}
you can implement some logic based on the error you get to throw an exception and catch it somewhere else in your code for example
when (error.errorCode) {
1-> throw MyCustomException(error.errorMessage)
else -> throw Exception(error.errorMessage)
}
I hope it helps
Upvotes: 16