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 { status: u16, error: Option<String>, message: Option<String> },
50}
51
52impl ApiError {
53 fn status_code(&self) -> StatusCode {
54 match self {
55 Self::InternalError | Self::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
56 Self::UpstreamFailure | Self::UpstreamUnavailable(_) => StatusCode::BAD_GATEWAY,
57 Self::UpstreamTimeout => StatusCode::GATEWAY_TIMEOUT,
58 Self::UpstreamError { status, .. } => {
59 StatusCode::from_u16(*status).unwrap_or(StatusCode::BAD_GATEWAY)
60 }
61 Self::AuthenticationRequired
62 | Self::AuthenticationFailed
63 | Self::AuthenticationFailedMsg(_)
64 | Self::InvalidToken
65 | Self::ExpiredToken
66 | Self::ExpiredTokenMsg(_)
67 | Self::TokenRequired
68 | Self::AccountDeactivated
69 | Self::AccountTakedown => StatusCode::UNAUTHORIZED,
70 Self::Forbidden | Self::InvitesDisabled => StatusCode::FORBIDDEN,
71 Self::AccountNotFound
72 | Self::RepoNotFound
73 | Self::RepoNotFoundMsg(_)
74 | Self::RecordNotFound
75 | Self::BlobNotFound
76 | Self::AppPasswordNotFound => StatusCode::NOT_FOUND,
77 Self::InvalidRequest(_)
78 | Self::InvalidHandle
79 | Self::HandleNotAvailable
80 | Self::HandleTaken
81 | Self::InvalidEmail
82 | Self::EmailTaken
83 | Self::InvalidInviteCode
84 | Self::DuplicateCreate
85 | Self::DuplicateAppPassword
86 | Self::InvalidSwap => StatusCode::BAD_REQUEST,
87 }
88 }
89
90 fn error_name(&self) -> &'static str {
91 match self {
92 Self::InternalError | Self::DatabaseError => "InternalError",
93 Self::UpstreamFailure | Self::UpstreamUnavailable(_) => "UpstreamFailure",
94 Self::UpstreamTimeout => "UpstreamTimeout",
95 Self::UpstreamError { error, .. } => {
96 if let Some(e) = error {
97 return Box::leak(e.clone().into_boxed_str());
98 }
99 "UpstreamError"
100 }
101 Self::AuthenticationRequired => "AuthenticationRequired",
102 Self::AuthenticationFailed | Self::AuthenticationFailedMsg(_) => "AuthenticationFailed",
103 Self::InvalidToken => "InvalidToken",
104 Self::ExpiredToken | Self::ExpiredTokenMsg(_) => "ExpiredToken",
105 Self::TokenRequired => "TokenRequired",
106 Self::AccountDeactivated => "AccountDeactivated",
107 Self::AccountTakedown => "AccountTakedown",
108 Self::Forbidden => "Forbidden",
109 Self::InvitesDisabled => "InvitesDisabled",
110 Self::AccountNotFound => "AccountNotFound",
111 Self::RepoNotFound | Self::RepoNotFoundMsg(_) => "RepoNotFound",
112 Self::RecordNotFound => "RecordNotFound",
113 Self::BlobNotFound => "BlobNotFound",
114 Self::AppPasswordNotFound => "AppPasswordNotFound",
115 Self::InvalidRequest(_) => "InvalidRequest",
116 Self::InvalidHandle => "InvalidHandle",
117 Self::HandleNotAvailable => "HandleNotAvailable",
118 Self::HandleTaken => "HandleTaken",
119 Self::InvalidEmail => "InvalidEmail",
120 Self::EmailTaken => "EmailTaken",
121 Self::InvalidInviteCode => "InvalidInviteCode",
122 Self::DuplicateCreate => "DuplicateCreate",
123 Self::DuplicateAppPassword => "DuplicateAppPassword",
124 Self::InvalidSwap => "InvalidSwap",
125 }
126 }
127
128 fn message(&self) -> Option<String> {
129 match self {
130 Self::AuthenticationFailedMsg(msg)
131 | Self::ExpiredTokenMsg(msg)
132 | Self::InvalidRequest(msg)
133 | Self::RepoNotFoundMsg(msg)
134 | Self::UpstreamUnavailable(msg) => Some(msg.clone()),
135 Self::UpstreamError { message, .. } => message.clone(),
136 Self::UpstreamTimeout => Some("Upstream service timed out".to_string()),
137 _ => None,
138 }
139 }
140
141 pub fn from_upstream_response(
142 status: u16,
143 body: &[u8],
144 ) -> Self {
145 if let Ok(parsed) = serde_json::from_slice::<serde_json::Value>(body) {
146 let error = parsed.get("error").and_then(|v| v.as_str()).map(String::from);
147 let message = parsed.get("message").and_then(|v| v.as_str()).map(String::from);
148 return Self::UpstreamError { status, error, message };
149 }
150 Self::UpstreamError { status, error: None, message: None }
151 }
152}
153
154impl IntoResponse for ApiError {
155 fn into_response(self) -> Response {
156 let body = ErrorBody {
157 error: self.error_name(),
158 message: self.message(),
159 };
160 (self.status_code(), Json(body)).into_response()
161 }
162}
163
164impl From<sqlx::Error> for ApiError {
165 fn from(e: sqlx::Error) -> Self {
166 tracing::error!("Database error: {:?}", e);
167 Self::DatabaseError
168 }
169}
170
171impl From<crate::auth::TokenValidationError> for ApiError {
172 fn from(e: crate::auth::TokenValidationError) -> Self {
173 match e {
174 crate::auth::TokenValidationError::AccountDeactivated => Self::AccountDeactivated,
175 crate::auth::TokenValidationError::AccountTakedown => Self::AccountTakedown,
176 crate::auth::TokenValidationError::KeyDecryptionFailed => Self::InternalError,
177 crate::auth::TokenValidationError::AuthenticationFailed => Self::AuthenticationFailed,
178 }
179 }
180}
181
182impl From<crate::util::DbLookupError> for ApiError {
183 fn from(e: crate::util::DbLookupError) -> Self {
184 match e {
185 crate::util::DbLookupError::NotFound => Self::AccountNotFound,
186 crate::util::DbLookupError::DatabaseError(db_err) => {
187 tracing::error!("Database error: {:?}", db_err);
188 Self::DatabaseError
189 }
190 }
191 }
192}