Skip to main content

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    Authn,
22    Authz,
23    BadFrame,
24    BadHeader,
25    BadJson,
26    BadPath,
27    BadProto,
28    BadQuery,
29    BasinDeletionPending,
30    BasinNotFound,
31    ClientHangup,
32    DecryptionFailed,
33    HotServer,
34    Invalid,
35    Other,
36    PermissionDenied,
37    QuotaExhausted,
38    RateLimited,
39    RequestTimeout,
40    ResourceAlreadyExists,
41    Storage,
42    StreamDeletionPending,
43    StreamNotFound,
44    TransactionConflict,
45    Unavailable,
46    UpstreamTimeout,
47}
48
49impl ErrorCode {
50    pub fn status(self) -> http::StatusCode {
51        match self {
52            Self::Authn => http::StatusCode::UNAUTHORIZED,
53            Self::DecryptionFailed
54            | Self::BadFrame
55            | Self::BadHeader
56            | Self::BadJson
57            | Self::BadPath
58            | Self::BadProto
59            | Self::BadQuery => http::StatusCode::BAD_REQUEST,
60            Self::Authz | Self::PermissionDenied | Self::QuotaExhausted => {
61                http::StatusCode::FORBIDDEN
62            }
63            Self::AccessTokenNotFound | Self::BasinNotFound | Self::StreamNotFound => {
64                http::StatusCode::NOT_FOUND
65            }
66            Self::RequestTimeout => http::StatusCode::REQUEST_TIMEOUT,
67            Self::BasinDeletionPending
68            | Self::ResourceAlreadyExists
69            | Self::StreamDeletionPending
70            | Self::TransactionConflict => http::StatusCode::CONFLICT,
71            Self::Invalid => http::StatusCode::UNPROCESSABLE_ENTITY,
72            Self::RateLimited => http::StatusCode::TOO_MANY_REQUESTS,
73            Self::ClientHangup => http::StatusCode::from_u16(499).expect("valid status code"),
74            Self::Other | Self::Storage => http::StatusCode::INTERNAL_SERVER_ERROR,
75            Self::HotServer => http::StatusCode::BAD_GATEWAY,
76            Self::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
77            Self::UpstreamTimeout => http::StatusCode::GATEWAY_TIMEOUT,
78        }
79    }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
84pub struct ErrorInfo {
85    pub code: &'static str,
86    pub message: String,
87}
88
89#[derive(Debug, Clone)]
90pub struct StandardError {
91    pub status: http::StatusCode,
92    pub info: ErrorInfo,
93}
94
95#[derive(Debug, Clone)]
96pub enum ErrorResponse {
97    AppendConditionFailed(super::stream::AppendConditionFailed),
98    Unwritten(super::stream::TailResponse),
99    Standard(StandardError),
100}
101
102impl ErrorResponse {
103    pub fn to_parts(&self) -> (http::StatusCode, String) {
104        let (status, res) = match self {
105            ErrorResponse::AppendConditionFailed(payload) => (
106                http::StatusCode::PRECONDITION_FAILED,
107                serde_json::to_string(&payload),
108            ),
109            ErrorResponse::Unwritten(payload) => (
110                http::StatusCode::RANGE_NOT_SATISFIABLE,
111                serde_json::to_string(&payload),
112            ),
113            ErrorResponse::Standard(err) => (err.status, serde_json::to_string(&err.info)),
114        };
115        (status, res.expect("basic json ser"))
116    }
117}
118
119#[cfg(feature = "axum")]
120impl axum::response::IntoResponse for ErrorResponse {
121    fn into_response(self) -> axum::response::Response {
122        let (status, json_str) = self.to_parts();
123        let mut response = (
124            [(
125                http::header::CONTENT_TYPE,
126                http::header::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
127            )],
128            json_str,
129        )
130            .into_response();
131        *response.status_mut() = status;
132        response
133    }
134}