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