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")]
18pub 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}