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 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}