Multiplatform Ktor schema declaration • opensavvy.spine.api • FailureSpec
FailureSpec¶
sealed interface FailureSpec
Declares the different ways an endpoint can fail.
An endpoint can declare zero or more ways in which it can fail:
val edit by patch()
.request<UserModificationDto>()
.failure<UserNotFound>(HttpStatusCode.NotFound)
.failure<NotAllowed>(HttpStatusCode.Forbidden)
Each of these failures is user-defined and can carry arbitrary data. Failures are declared in an endpoint with the AnyEndpoint.Builder.failure function.
Declaring a failure by HTTP code¶
Each endpoint declares its failures and which HTTP status code they each correspond to. When the server throws that failure, it will be returned using that HTTP code. When the client receives that HTTP code, it will attempt to deserialize that failure from the body.
Any Kotlin object that Ktor can serialize can be used as a failure type.
@Serializable
data class NotAllowed(val reason: String)
val listUsers by get()
.response<List<UserDto>>()
.failure<NotAllowed>(HttpStatusCode.Forbidden)
However, since the client uses the status code to decide what to deserialize, it is not possible to declare two different failures under the same status code. Instead, we can declare a single failure represented by a sealed class or sealed interface and let KotlinX.Serialization decide which is which:
@Serializable
sealed class CannotProcessUserModification {
@Serializable
@SerialName("InvalidUsername")
data class InvalidUsername(val userId: String, val explain: String) : CannotProcessUserModification()
@Serializable
@SerialName("InvalidAge")
data class InvalidAge(val userId: String, val explain: String) : CannotProcessUserModification()
}
val edit by patch()
.request<UserModification>()
.failure<CannotProcessUserModification>(HttpStatusCode.UnprocessableEntity)
With this approach, each endpoint that declares a particular failure must redeclare which HTTP status code it corresponds to. At scale, this may cause inconsistencies where the same failure is used for different status codes in different endpoints. To avoid this, see FailureCompanion.
Inheritors¶
Types¶
ByCode¶
interface ByCode<out F> : FailureSpec
Represents a failure that is represented by a specific statusCode.
Never¶
object Never : FailureSpec
The no-operation FailureSpec: this failure type can never happen.
Or¶
class Or<out A : FailureSpec, out B : FailureSpec>(val a: A, val b: B) : FailureSpec
Combines multiple failure specifications into a single one.