diff --git a/client/src/commonMain/kotlin/SpineResponse.kt b/client/src/commonMain/kotlin/SpineResponse.kt index 6731d79..3e241ac 100644 --- a/client/src/commonMain/kotlin/SpineResponse.kt +++ b/client/src/commonMain/kotlin/SpineResponse.kt @@ -7,22 +7,68 @@ import opensavvy.spine.api.FailureSpec import opensavvy.spine.api.FailureSpec.Never import opensavvy.spine.api.FailureSpec.Or -class SpineResponse<@Suppress("unused") Out : Any, out @Suppress("unused") Failure : FailureSpec>( +/** + * The type returned by [request], encapsulating an HTTP response. + * + * Most functionality on this type is provided by extensions. + */ +class SpineResponse<@Suppress("unused") Out : Any, @Suppress("unused") out Failure : FailureSpec>( + /** + * Ktor's underlying [HttpResponse]. + */ val httpResponse: HttpResponse, + + /** + * The [FailureSpec] that was defined in the endpoint that was called. + */ val failureSpec: Failure, ) { + /** + * `true` if the HTTP status code is a success. `false` otherwise. + */ val isSuccessful get() = httpResponse.status.isSuccess() } +/** + * Deserializes the response body, returning `null` if the response is not successful. + * + * The Ktor `ContentNegotation` plugin may be necessary for this function to work. + * + * ### Example + * + * ```kotlin + * client.request(Api / Users / Users.list).bodyOrNull() + * ``` + * + * @see bodyOrThrow Throw an exception on failure. + * @see handle Handle failures exhaustively. + */ suspend inline fun SpineResponse.bodyOrNull(): Out? = if (isSuccessful) httpResponse.call.body() else null +/** + * Deserializes the response body, throwing a [SpineReceptionException] if the response is not successful. + * + * The Ktor `ContentNegotation` plugin may be necessary for this function to work. + * + * ### Example + * + * ```kotlin + * client.request(Api / Users / Users.list).bodyOrThrow() + * ``` + * + * @see bodyOrNull Return `null` on failure. + * @see handle Handle failures exhaustively. + */ suspend inline fun SpineResponse.bodyOrThrow(): Out = if (isSuccessful) httpResponse.call.body() else throw SpineReceptionException(httpResponse, httpResponse.bodyAsText()) +/** + * The exception thrown by [bodyOrThrow]. + */ class SpineReceptionException( val response: HttpResponse, body: String, @@ -48,12 +94,50 @@ internal suspend inline fun handleFailureUnchecked( return handle(response.httpResponse.body()) } +/** + * Exhaustively handle failures. + * + * If the request is succesful, [transform] is called. + * If the request fails with the error [F1], [handle1] is called. + * + * To learn more about failures, see [the documentation](https://spine.opensavvy.dev/failures.html). + * + * ### Example + * + * ```kotlin + * client.request(Api / Users / Users.create).handle( + * handle1 = { println("Failed with $it") }, + * ) { println("Created: " + it) } + * ``` + * + * @see bodyOrNull Treat all failures as `null`. + * @see bodyOrThrow Treat all failures as exceptions. + */ suspend inline fun SpineResponse>>.handle( handle1: (F1) -> O, transform: (Out) -> O, ) : O = handleFailureUnchecked(this, failureSpec.b, handle1) ?: handleFinal(transform) +/** + * Exhaustively handle failures. + * + * If the request is succesful, [transform] is called. + * If the request fails with an error, the corresponding handler is called. + * + * To learn more about failures, see [the documentation](https://spine.opensavvy.dev/failures.html). + * + * ### Example + * + * ```kotlin + * client.request(Api / Users / Users.create).handle( + * handle1 = { println("Failed with $it") }, + * ) { println("Created: " + it) } + * ``` + * + * @see bodyOrNull Treat all failures as `null`. + * @see bodyOrThrow Treat all failures as exceptions. + */ suspend inline fun SpineResponse>, FailureSpec.ByCode>>.handle( handle1: (F1) -> O, handle2: (F2) -> O, @@ -62,6 +146,25 @@ suspend inline fun SpineResponse< ?: handleFailureUnchecked(this, failureSpec.b, handle2) ?: handleFinal(transform) +/** + * Exhaustively handle failures. + * + * If the request is succesful, [transform] is called. + * If the request fails with an error, the corresponding handler is called. + * + * To learn more about failures, see [the documentation](https://spine.opensavvy.dev/failures.html). + * + * ### Example + * + * ```kotlin + * client.request(Api / Users / Users.create).handle( + * handle1 = { println("Failed with $it") }, + * ) { println("Created: " + it) } + * ``` + * + * @see bodyOrNull Treat all failures as `null`. + * @see bodyOrThrow Treat all failures as exceptions. + */ suspend inline fun SpineResponse>, FailureSpec.ByCode>, FailureSpec.ByCode>>.handle( handle1: (F1) -> O, handle2: (F2) -> O, @@ -72,6 +175,25 @@ suspend inline fun Sp ?: handleFailureUnchecked(this, failureSpec.b, handle3) ?: handleFinal(transform) +/** + * Exhaustively handle failures. + * + * If the request is succesful, [transform] is called. + * If the request fails with an error, the corresponding handler is called. + * + * To learn more about failures, see [the documentation](https://spine.opensavvy.dev/failures.html). + * + * ### Example + * + * ```kotlin + * client.request(Api / Users / Users.create).handle( + * handle1 = { println("Failed with $it") }, + * ) { println("Created: " + it) } + * ``` + * + * @see bodyOrNull Treat all failures as `null`. + * @see bodyOrThrow Treat all failures as exceptions. + */ suspend inline fun SpineResponse>, FailureSpec.ByCode>, FailureSpec.ByCode>, FailureSpec.ByCode>>.handle( handle1: (F1) -> O, handle2: (F2) -> O, @@ -84,6 +206,25 @@ suspend inline fun SpineResponse>, FailureSpec.ByCode>, FailureSpec.ByCode>, FailureSpec.ByCode>, FailureSpec.ByCode>>.handle( handle1: (F1) -> O, handle2: (F2) -> O, @@ -98,6 +239,25 @@ suspend inline fun SpineResponse>, FailureSpec.ByCode>, FailureSpec.ByCode>, FailureSpec.ByCode>, FailureSpec.ByCode>, FailureSpec.ByCode>>.handle( handle1: (F1) -> O, handle2: (F2) -> O,