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