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