this repo has no description
1use axum::{
2 Json,
3 http::StatusCode,
4 response::{IntoResponse, Response},
5};
6use serde::Serialize;
7
8#[derive(Debug, Serialize)]
9struct ErrorBody {
10 error: &'static str,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 message: Option<String>,
13}
14
15#[derive(Debug)]
16pub enum ApiError {
17 InternalError,
18 AuthenticationRequired,
19 AuthenticationFailed,
20 AuthenticationFailedMsg(String),
21 InvalidRequest(String),
22 InvalidToken,
23 ExpiredToken,
24 ExpiredTokenMsg(String),
25 TokenRequired,
26 AccountDeactivated,
27 AccountTakedown,
28 AccountNotFound,
29 RepoNotFound,
30 RepoNotFoundMsg(String),
31 RecordNotFound,
32 BlobNotFound,
33 InvalidHandle,
34 HandleNotAvailable,
35 HandleTaken,
36 InvalidEmail,
37 EmailTaken,
38 InvalidInviteCode,
39 DuplicateCreate,
40 DuplicateAppPassword,
41 AppPasswordNotFound,
42 InvalidSwap,
43 Forbidden,
44 InvitesDisabled,
45 DatabaseError,
46 UpstreamFailure,
47 UpstreamTimeout,
48 UpstreamUnavailable(String),
49 UpstreamError {
50 status: u16,
51 error: Option<String>,
52 message: Option<String>,
53 },
54}
55
56impl ApiError {
57 fn status_code(&self) -> StatusCode {
58 match self {
59 Self::InternalError | Self::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
60 Self::UpstreamFailure | Self::UpstreamUnavailable(_) => StatusCode::BAD_GATEWAY,
61 Self::UpstreamTimeout => StatusCode::GATEWAY_TIMEOUT,
62 Self::UpstreamError { status, .. } => {
63 StatusCode::from_u16(*status).unwrap_or(StatusCode::BAD_GATEWAY)
64 }
65 Self::AuthenticationRequired
66 | Self::AuthenticationFailed
67 | Self::AuthenticationFailedMsg(_)
68 | Self::InvalidToken
69 | Self::ExpiredToken
70 | Self::ExpiredTokenMsg(_)
71 | Self::TokenRequired
72 | Self::AccountDeactivated
73 | Self::AccountTakedown => StatusCode::UNAUTHORIZED,
74 Self::Forbidden | Self::InvitesDisabled => StatusCode::FORBIDDEN,
75 Self::AccountNotFound
76 | Self::RepoNotFound
77 | Self::RepoNotFoundMsg(_)
78 | Self::RecordNotFound
79 | Self::BlobNotFound
80 | Self::AppPasswordNotFound => StatusCode::NOT_FOUND,
81 Self::InvalidRequest(_)
82 | Self::InvalidHandle
83 | Self::HandleNotAvailable
84 | Self::HandleTaken
85 | Self::InvalidEmail
86 | Self::EmailTaken
87 | Self::InvalidInviteCode
88 | Self::DuplicateCreate
89 | Self::DuplicateAppPassword
90 | Self::InvalidSwap => StatusCode::BAD_REQUEST,
91 }
92 }
93 fn error_name(&self) -> &'static str {
94 match self {
95 Self::InternalError | Self::DatabaseError => "InternalError",
96 Self::UpstreamFailure | Self::UpstreamUnavailable(_) => "UpstreamFailure",
97 Self::UpstreamTimeout => "UpstreamTimeout",
98 Self::UpstreamError { error, .. } => {
99 if let Some(e) = error {
100 return Box::leak(e.clone().into_boxed_str());
101 }
102 "UpstreamError"
103 }
104 Self::AuthenticationRequired => "AuthenticationRequired",
105 Self::AuthenticationFailed | Self::AuthenticationFailedMsg(_) => "AuthenticationFailed",
106 Self::InvalidToken => "InvalidToken",
107 Self::ExpiredToken | Self::ExpiredTokenMsg(_) => "ExpiredToken",
108 Self::TokenRequired => "TokenRequired",
109 Self::AccountDeactivated => "AccountDeactivated",
110 Self::AccountTakedown => "AccountTakedown",
111 Self::Forbidden => "Forbidden",
112 Self::InvitesDisabled => "InvitesDisabled",
113 Self::AccountNotFound => "AccountNotFound",
114 Self::RepoNotFound | Self::RepoNotFoundMsg(_) => "RepoNotFound",
115 Self::RecordNotFound => "RecordNotFound",
116 Self::BlobNotFound => "BlobNotFound",
117 Self::AppPasswordNotFound => "AppPasswordNotFound",
118 Self::InvalidRequest(_) => "InvalidRequest",
119 Self::InvalidHandle => "InvalidHandle",
120 Self::HandleNotAvailable => "HandleNotAvailable",
121 Self::HandleTaken => "HandleTaken",
122 Self::InvalidEmail => "InvalidEmail",
123 Self::EmailTaken => "EmailTaken",
124 Self::InvalidInviteCode => "InvalidInviteCode",
125 Self::DuplicateCreate => "DuplicateCreate",
126 Self::DuplicateAppPassword => "DuplicateAppPassword",
127 Self::InvalidSwap => "InvalidSwap",
128 }
129 }
130 fn message(&self) -> Option<String> {
131 match self {
132 Self::AuthenticationFailedMsg(msg)
133 | Self::ExpiredTokenMsg(msg)
134 | Self::InvalidRequest(msg)
135 | Self::RepoNotFoundMsg(msg)
136 | Self::UpstreamUnavailable(msg) => Some(msg.clone()),
137 Self::UpstreamError { message, .. } => message.clone(),
138 Self::UpstreamTimeout => Some("Upstream service timed out".to_string()),
139 _ => None,
140 }
141 }
142 pub fn from_upstream_response(status: u16, body: &[u8]) -> Self {
143 if let Ok(parsed) = serde_json::from_slice::<serde_json::Value>(body) {
144 let error = parsed
145 .get("error")
146 .and_then(|v| v.as_str())
147 .map(String::from);
148 let message = parsed
149 .get("message")
150 .and_then(|v| v.as_str())
151 .map(String::from);
152 return Self::UpstreamError {
153 status,
154 error,
155 message,
156 };
157 }
158 Self::UpstreamError {
159 status,
160 error: None,
161 message: None,
162 }
163 }
164}
165
166impl IntoResponse for ApiError {
167 fn into_response(self) -> Response {
168 let body = ErrorBody {
169 error: self.error_name(),
170 message: self.message(),
171 };
172 (self.status_code(), Json(body)).into_response()
173 }
174}
175
176impl From<sqlx::Error> for ApiError {
177 fn from(e: sqlx::Error) -> Self {
178 tracing::error!("Database error: {:?}", e);
179 Self::DatabaseError
180 }
181}
182
183impl From<crate::auth::TokenValidationError> for ApiError {
184 fn from(e: crate::auth::TokenValidationError) -> Self {
185 match e {
186 crate::auth::TokenValidationError::AccountDeactivated => Self::AccountDeactivated,
187 crate::auth::TokenValidationError::AccountTakedown => Self::AccountTakedown,
188 crate::auth::TokenValidationError::KeyDecryptionFailed => Self::InternalError,
189 crate::auth::TokenValidationError::AuthenticationFailed => Self::AuthenticationFailed,
190 }
191 }
192}
193
194impl From<crate::util::DbLookupError> for ApiError {
195 fn from(e: crate::util::DbLookupError) -> Self {
196 match e {
197 crate::util::DbLookupError::NotFound => Self::AccountNotFound,
198 crate::util::DbLookupError::DatabaseError(db_err) => {
199 tracing::error!("Database error: {:?}", db_err);
200 Self::DatabaseError
201 }
202 }
203 }
204}