s2_api/v1/
error.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(
4    Debug,
5    Clone,
6    Copy,
7    PartialEq,
8    Eq,
9    Hash,
10    Serialize,
11    Deserialize,
12    strum::Display,
13    strum::EnumString,
14    strum::IntoStaticStr,
15)]
16#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
17#[strum(serialize_all = "snake_case")]
18// Keep this alphabetized.
19pub enum ErrorCode {
20    AccessTokenNotFound,
21    BadFrame,
22    BadHeader,
23    BadJson,
24    BadPath,
25    BadProto,
26    BadQuery,
27    BasinDeletionPending,
28    BasinNotFound,
29    ClientHangup,
30    HotServer,
31    Invalid,
32    Other,
33    PermissionDenied,
34    QuotaExhausted,
35    RateLimited,
36    ResourceAlreadyExists,
37    Storage,
38    StreamDeletionPending,
39    StreamNotFound,
40    Timeout,
41    TransactionConflict,
42    Unavailable,
43}
44
45impl ErrorCode {
46    pub fn status(self) -> http::StatusCode {
47        match self {
48            Self::BadFrame
49            | Self::BadHeader
50            | Self::BadJson
51            | Self::BadPath
52            | Self::BadProto
53            | Self::BadQuery => http::StatusCode::BAD_REQUEST,
54            Self::Invalid => http::StatusCode::UNPROCESSABLE_ENTITY,
55            Self::AccessTokenNotFound | Self::BasinNotFound | Self::StreamNotFound => {
56                http::StatusCode::NOT_FOUND
57            }
58            Self::BasinDeletionPending
59            | Self::ResourceAlreadyExists
60            | Self::StreamDeletionPending
61            | Self::TransactionConflict => http::StatusCode::CONFLICT,
62            Self::ClientHangup => http::StatusCode::from_u16(499).expect("valid status code"),
63            Self::PermissionDenied | Self::QuotaExhausted => http::StatusCode::FORBIDDEN,
64            Self::RateLimited => http::StatusCode::TOO_MANY_REQUESTS,
65            Self::HotServer => http::StatusCode::BAD_GATEWAY,
66            Self::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
67            Self::Other | Self::Storage => http::StatusCode::INTERNAL_SERVER_ERROR,
68            Self::Timeout => http::StatusCode::REQUEST_TIMEOUT,
69        }
70    }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
75pub struct ErrorInfo {
76    pub code: &'static str,
77    pub message: String,
78}
79
80#[derive(Debug, Clone)]
81pub struct GenericError {
82    pub status: http::StatusCode,
83    pub info: ErrorInfo,
84}
85
86#[derive(Debug, Clone)]
87pub enum ErrorResponse {
88    AppendConditionFailed(super::stream::AppendConditionFailed),
89    Unwritten(super::stream::TailResponse),
90    Generic(GenericError),
91}
92
93impl ErrorResponse {
94    pub fn to_parts(&self) -> (http::StatusCode, String) {
95        let (status, res) = match self {
96            ErrorResponse::AppendConditionFailed(payload) => (
97                http::StatusCode::PRECONDITION_FAILED,
98                serde_json::to_string(&payload),
99            ),
100            ErrorResponse::Unwritten(payload) => (
101                http::StatusCode::RANGE_NOT_SATISFIABLE,
102                serde_json::to_string(&payload),
103            ),
104            ErrorResponse::Generic(err) => (err.status, serde_json::to_string(&err.info)),
105        };
106        (status, res.expect("basic json ser"))
107    }
108}
109
110#[cfg(feature = "axum")]
111impl axum::response::IntoResponse for ErrorResponse {
112    fn into_response(self) -> axum::response::Response {
113        let (status, json_str) = self.to_parts();
114        let mut response = (
115            [(
116                http::header::CONTENT_TYPE,
117                http::header::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
118            )],
119            json_str,
120        )
121            .into_response();
122        *response.status_mut() = status;
123        response
124    }
125}