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}