#[derive(thiserror::Error, Debug)] pub enum Error { #[error("Database error: {0}")] Sqlx(#[source] sqlx::Error), #[error("Route not found: {0}")] RouteNotFound(axum::http::Uri), #[error("User not found")] UserNotFound, #[error("Task not found")] TaskNotFound, #[error("Required header not found: {0}")] HeaderNotFound(axum::http::HeaderName), #[error("Failed to parse header: {0} (wrong token type?)")] Header(axum_extra::typed_header::TypedHeaderRejection), #[error("Invalid user token")] InvalidToken, #[error("User with that email already exists")] UserExists, #[error("Invalid email: {0}")] EmailInvalid(#[from] email_address::Error), #[error("Failed to reach authentication server: {0}")] AuthRequest(#[from] axum::http::Error), #[error("Authentication error: {0}")] Auth(#[from] crate::auth::error::Error), } impl From for Error { fn from(value: sqlx::Error) -> Self { match value { sqlx::Error::Database(db_err) if db_err.is_unique_violation() && db_err.table().is_some_and(|s| s == "user_") => { Error::UserExists } err => Error::Sqlx(err), } } } impl From for Error { fn from(value: axum_extra::typed_header::TypedHeaderRejection) -> Self { if value.is_missing() { Self::HeaderNotFound(value.name().clone()) } else { Self::Header(value) } } } impl axum::response::IntoResponse for Error { fn into_response(self) -> axum::response::Response { use axum::http::header::AUTHORIZATION; use axum::http::StatusCode; let status = match self { Self::RouteNotFound(_) | Self::UserNotFound | Self::TaskNotFound => { StatusCode::NOT_FOUND } Self::UserExists => StatusCode::CONFLICT, Self::InvalidToken => StatusCode::UNAUTHORIZED, Self::HeaderNotFound(ref h) if h == AUTHORIZATION => StatusCode::UNAUTHORIZED, Self::HeaderNotFound(_) => StatusCode::BAD_REQUEST, Self::EmailInvalid(_) | Self::Header(_) => StatusCode::UNPROCESSABLE_ENTITY, Self::AuthRequest(_) | Self::Sqlx(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::Auth(err) => return err.into_response(), }; (status, self.to_string()).into_response() } }