Skip to content

Commit

Permalink
fix: Improve error handling in Transport.kt (#1540)
Browse files Browse the repository at this point in the history
- Provide HTTP request and response details on SDKErrorResponse.
- Provide the cause of SDKError for chaining and re-throwing
- Add LookerApiException, which includes the same request/response
information as SDKErrorResponse.
- Mark ok() as Deprecated, because it throws java.lang.Error, which is
not something application code should do.
- Add SDKResponse::getOrThrow() instance method, patterned after
Kotlin's built-in Result type, which handles SDKErrorResponse by
throwing the newly introduced LookerApiException

Fixes #1539  🦕
  • Loading branch information
haywood authored and drstrangelooker committed Dec 2, 2024
1 parent 1436368 commit a091549
Showing 1 changed file with 58 additions and 6 deletions.
64 changes: 58 additions & 6 deletions kotlin/src/main/com/looker/rtl/Transport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,67 @@ sealed class SDKResponse {
data class SDKErrorResponse<T>(
/** The error object returned by the SDK call. */
val value: T,
val method: HttpMethod,
val path: String,
val statusCode: Int,
val statusMessage: String,
val responseHeaders: HttpHeaders,
val responseBody: String,
) : SDKResponse() {
/** Whether the SDK call was successful. */
val ok: Boolean = false
}

/** An error representing an issue in the SDK, like a network or parsing error. */
data class SDKError(val message: String) : SDKResponse() {
data class SDKError(val message: String, val cause: Exception) : SDKResponse() {
val type: String = "sdk_error"
}

inline fun <reified V> getOrThrow(): V =
when (this) {
is SDKResponse.SDKSuccessResponse<*> ->
checkNotNull(value as? V) {
if (value == null) {
"Expected value of type ${V::class}, but was null"
} else {
"Expected value of type ${V::class}, but was ${value::class}"
}
}

is SDKResponse.SDKErrorResponse<*> ->
throw LookerApiException(
method,
path,
statusCode,
statusMessage,
responseHeaders,
responseBody,
)

is SDKResponse.SDKError -> throw cause
}

companion object {
const val ERROR_BODY = "error_body"
}
}

/**
* Response handler that throws an error on error response, returns success result on success
*/
/** Thrown when a Looker API call returns an error. */
data class LookerApiException(
val method: HttpMethod,
val path: String,
val statusCode: Int,
val statusMessage: String,
val responseHeaders: HttpHeaders,
val responseBody: String,
) : Exception() {
override val message = "$method $path $statusCode: $statusMessage"
}

/** Response handler that throws an error on error response, returns success result on success */
@Deprecated(
"This method throws java.lang.Error, which is not recommended for use in application code. Please use SDKResponse.getOrThrow() instead."
)
fun <T> ok(response: SDKResponse): T {
@Suppress("UNCHECKED_CAST")
when (response) {
Expand Down Expand Up @@ -379,9 +423,17 @@ open class Transport(val options: TransportOptions) {
}
SDKResponse.SDKSuccessResponse(rawResult)
} catch (e: HttpResponseException) {
SDKResponse.SDKErrorResponse("$method $path $ERROR_BODY: ${e.content}")
SDKResponse.SDKErrorResponse(
"$method $path $ERROR_BODY: ${e.content}",
method,
path,
e.statusCode,
e.statusMessage,
e.headers,
e.content,
)
} catch (e: Exception) {
SDKResponse.SDKError(e.message ?: "Something went wrong")
SDKResponse.SDKError(e.message ?: "Something went wrong", e)
}

return sdkResponse
Expand Down

0 comments on commit a091549

Please sign in to comment.