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