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}