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