#[derive(thiserror::Error, Debug)] pub enum Error { #[error("Failed to parse header: {0}")] HeaderValue(#[from] axum::http::header::InvalidHeaderValue), #[error("Required header not found: {0}")] HeaderNotFound(axum::http::HeaderName), #[error("Failed to parse header: {0} (wrong token type?)")] HeaderRejection(axum_extra::typed_header::TypedHeaderRejection), #[error("Database error: {0}")] Sqlx(#[from] sqlx::Error), #[error("Time error: {0}")] Time(#[from] time::error::ComponentRange), #[error("Invalid authorization token: {0}")] JwtValidation(#[source] jsonwebtoken::errors::Error), #[error("Invalid authorization token format: {0}")] JwtFormat(#[source] jsonwebtoken::errors::Error), #[error("JWT server error: {0}")] JwtInternal(#[source] jsonwebtoken::errors::Error), #[error("Failed to hash password: {0}")] PasswordHash(#[source] argon2::password_hash::Error), #[error("Invalid UUID: {0}")] Uuid(String), #[error("Failed to register user")] Registration, #[error("Invalid email or password")] LoginInvalid, #[error("Authorization token not found")] JwtNotFound, #[error("The user belonging to this token no longer exists")] UserNotFound, } impl From for Error { fn from(value: axum_extra::typed_header::TypedHeaderRejection) -> Self { if value.is_missing() { Self::HeaderNotFound(value.name().clone()) } else { Self::HeaderRejection(value) } } } impl From for Error { fn from(value: argon2::password_hash::Error) -> Self { match value { argon2::password_hash::Error::Password => Self::LoginInvalid, _ => Self::PasswordHash(value), } } } impl From for Error { fn from(value: jsonwebtoken::errors::Error) -> Self { use jsonwebtoken::errors::ErrorKind; match value.kind() { ErrorKind::InvalidSignature | ErrorKind::MissingRequiredClaim(_) | ErrorKind::ExpiredSignature | ErrorKind::InvalidIssuer | ErrorKind::InvalidAudience | ErrorKind::InvalidSubject | ErrorKind::ImmatureSignature | ErrorKind::InvalidAlgorithm | ErrorKind::MissingAlgorithm => Error::JwtValidation(value), ErrorKind::InvalidToken => Error::JwtFormat(value), _ => Error::JwtInternal(value), } } } impl From for Error { fn from(value: uuid::Error) -> Self { Error::Uuid(value.to_string()) } } impl axum::response::IntoResponse for Error { fn into_response(self) -> axum::response::Response { use axum::http::{header::AUTHORIZATION, StatusCode}; let status = match self { Self::HeaderNotFound(ref h) if h == AUTHORIZATION => StatusCode::UNAUTHORIZED, Self::HeaderNotFound(_) => StatusCode::BAD_REQUEST, Self::HeaderRejection(_) | Error::JwtFormat(_) | Error::Uuid(_) => { StatusCode::UNPROCESSABLE_ENTITY } Error::JwtValidation(_) | Error::LoginInvalid | Error::UserNotFound | Error::JwtNotFound => StatusCode::UNAUTHORIZED, _ => StatusCode::INTERNAL_SERVER_ERROR, }; (status, self.to_string()).into_response() } }