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