use std::fmt::{self, Display}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; macro_rules! forbidden { ($message:literal) => { crate::errors::AppError::Forbidden($message.to_owned()) }; ($message:literal, $($param:expr),+) => { crate::errors::AppError::Forbidden(format!($message, $($param)+)) }; } macro_rules! not_found { ($message:literal) => { crate::errors::AppError::NotFound($message.to_owned()) }; ($message:literal, $($param:expr),+) => { crate::errors::AppError::NotFound(format!($message, $($param)+)) }; } macro_rules! bad_request { ($message:literal) => { crate::errors::AppError::BadRequest(format!($message)) }; ($message:literal, $($param:expr),+) => { crate::errors::AppError::BadRequest(format!($message, $($param)+)) }; } pub(crate) use bad_request; pub(crate) use forbidden; pub(crate) use not_found; /// Custom error type that maps to appropriate HTTP responses. #[derive(Debug)] pub enum AppError { InternalServerError(anyhow::Error), Forbidden(String), NotFound(String), BadRequest(String), TooManyRequests(String), } impl IntoResponse for AppError { fn into_response(self) -> Response { match self { Self::InternalServerError(err) => { tracing::error!("Application error: {:?}", err); (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() } Self::Forbidden(client_message) => { tracing::info!("Forbidden: {}", client_message); (StatusCode::FORBIDDEN, client_message).into_response() } Self::NotFound(client_message) => { tracing::info!("Not found: {}", client_message); (StatusCode::NOT_FOUND, client_message).into_response() } Self::TooManyRequests(client_message) => { // Debug level so that if this is from a runaway loop, it won't // overwhelm production logs. tracing::debug!("Too many requests: {}", client_message); (StatusCode::TOO_MANY_REQUESTS, client_message).into_response() } Self::BadRequest(client_message) => { tracing::info!("Bad user input: {}", client_message); (StatusCode::BAD_REQUEST, client_message).into_response() } } } } // Easily convert semi-arbitrary errors to InternalServerError. impl From for AppError where E: Into, { fn from(err: E) -> Self { Self::InternalServerError(Into::::into(err)) } } impl Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AppError::InternalServerError(inner) => inner.fmt(f), AppError::Forbidden(client_message) => { write!(f, "ForbiddenError: {client_message}") } AppError::NotFound(client_message) => { write!(f, "NotFoundError: {client_message}") } AppError::BadRequest(client_message) => { write!(f, "BadRequestError: {client_message}") } AppError::TooManyRequests(client_message) => { write!(f, "TooManyRequestsError: {client_message}") } } } }