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 | Self::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
66 Self::Other | Self::Storage => http::StatusCode::INTERNAL_SERVER_ERROR,
67 Self::Timeout => http::StatusCode::REQUEST_TIMEOUT,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
74pub struct ErrorInfo {
75 pub code: &'static str,
76 pub message: String,
77}
78
79#[derive(Debug, Clone)]
80pub struct GenericError {
81 pub status: http::StatusCode,
82 pub info: ErrorInfo,
83}
84
85#[derive(Debug, Clone)]
86pub enum ErrorResponse {
87 AppendConditionFailed(super::stream::AppendConditionFailed),
88 Unwritten(super::stream::TailResponse),
89 Generic(GenericError),
90}
91
92impl ErrorResponse {
93 pub fn to_parts(&self) -> (http::StatusCode, String) {
94 let (status, res) = match self {
95 ErrorResponse::AppendConditionFailed(payload) => (
96 http::StatusCode::PRECONDITION_FAILED,
97 serde_json::to_string(&payload),
98 ),
99 ErrorResponse::Unwritten(payload) => (
100 http::StatusCode::RANGE_NOT_SATISFIABLE,
101 serde_json::to_string(&payload),
102 ),
103 ErrorResponse::Generic(err) => (err.status, serde_json::to_string(&err.info)),
104 };
105 (status, res.expect("basic json ser"))
106 }
107}
108
109#[cfg(feature = "axum")]
110impl axum::response::IntoResponse for ErrorResponse {
111 fn into_response(self) -> axum::response::Response {
112 let (status, json_str) = self.to_parts();
113 let mut response = (
114 [(
115 http::header::CONTENT_TYPE,
116 http::header::HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
117 )],
118 json_str,
119 )
120 .into_response();
121 *response.status_mut() = status;
122 response
123 }
124}