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