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