Created
September 4, 2022 18:54
-
-
Save osipxd/ae979d4978af717686671e285b6e3534 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
package io.example | |
import kotlinx.serialization.Serializable | |
import kotlinx.serialization.json.Json | |
import kotlinx.serialization.json.decodeFromStream | |
import okhttp3.Interceptor | |
import okhttp3.Response | |
import retrofit2.HttpException | |
import retrofit2.Invocation | |
import java.io.IOException | |
import java.io.InputStream | |
import kotlin.math.abs | |
import retrofit2.Response as RetrofitResponse | |
class ServerErrorsInterceptor( | |
private val json: Json, | |
) : Interceptor { | |
override fun intercept(chain: Interceptor.Chain): Response { | |
val response = chain.proceed(chain.request()) | |
if (!response.isSuccessful) throwErrorForResponse(response) | |
return response | |
} | |
private fun throwErrorForResponse(response: Response) { | |
// We can't call body.string() because it closes stream and makes it | |
// unavailable for other interceptors and for retrofit. | |
// Read response body to buffer instead. | |
val source = response.body?.source() ?: return | |
source.request(Long.MAX_VALUE) // Add whole response to buffer | |
val bodyStream = source.buffer.clone().inputStream() | |
val errorResponse = parseErrorResponse(bodyStream) | |
throw ServerException( | |
message = errorResponse.error, | |
userMessage = errorResponse.message, | |
errorCode = errorResponse.code, | |
httpException = response.toHttpException(), | |
) | |
} | |
private fun parseErrorResponse(stream: InputStream): ErrorResponse { | |
// Specify here your logic to parse error body. Simplest possible logic used as example | |
return json.decodeFromStream(stream) | |
} | |
@Suppress("NOTHING_TO_INLINE") // We don't want this call in stacktrace, so make it inline | |
private inline fun Response.toHttpException(): HttpException { | |
val retrofitResponse = RetrofitResponse.error<Unit>(checkNotNull(body), this) | |
return HttpException(retrofitResponse) | |
.prependStackTrace(getRetrofitStackTraceElement()) | |
} | |
private fun Response.getRetrofitStackTraceElement(): StackTraceElement? { | |
val method = (request.tag(Invocation::class.java) ?: return null).method() | |
// Kotlin adds suffix after hyphen for internal methods and methods having default arguments. Drop it. | |
// Also, append response code to the method name. | |
val fakeMethodName = "${method.name.substringBefore('-')}-$code" | |
// We don't know real line number for method, so we calculate fake line number. | |
// This value should be a positive number and should always be the same for equal method calls. | |
val fakeLineNumber = abs(method.declaringClass.name.hashCode() xor fakeMethodName.hashCode()) | |
return StackTraceElement( | |
/* declaringClass = */ method.declaringClass.name, | |
/* methodName = */ fakeMethodName, | |
/* fileName = */ "${method.declaringClass.simpleName}.kt", | |
/* lineNumber = */ fakeLineNumber, | |
) | |
} | |
} | |
@Serializable | |
data class ErrorResponse( | |
val code: String? = null, | |
val message: String? = null, | |
val error: String? = null, | |
) | |
class ServerException( | |
message: String?, | |
val userMessage: String?, | |
val errorCode: String?, | |
val httpException: HttpException, | |
) : IOException(message, httpException) { | |
val httpCode: Int | |
get() = httpException.code() | |
} |
This file contains hidden or 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
package io.example | |
/** Adds the given [elements] to the start of the given stack trace. */ | |
fun <T : Throwable> T.prependStackTrace(vararg elements: StackTraceElement?): T = apply { | |
stackTrace = (elements.filterNotNull() + stackTrace).toTypedArray() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment