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