use std::fmt::{self, Display}; use axum::http::StatusCode; use axum::response::{IntoResponse, Redirect, Response}; use validator::ValidationErrors; #[derive(Debug)] pub struct AuthRedirectInfo { base_path: String, } /// Custom error type that maps to appropriate HTTP responses. #[derive(Debug)] pub enum AppError { InternalServerError(anyhow::Error), ForbiddenError(String), NotFoundError(String), BadRequestError(String), TooManyRequestsError(String), AuthRedirect(AuthRedirectInfo), } impl AppError { pub fn auth_redirect_from_base_path(base_path: String) -> Self { Self::AuthRedirect(AuthRedirectInfo { base_path }) } pub fn from_validation_errors(errs: ValidationErrors) -> Self { // TODO: customize validation errors formatting Self::BadRequestError( serde_json::to_string(&errs).unwrap_or("validation error".to_string()), ) } } impl IntoResponse for AppError { fn into_response(self) -> Response { match self { Self::AuthRedirect(AuthRedirectInfo { base_path }) => { tracing::debug!("Handling AuthRedirect"); Redirect::to(&format!("{}/auth/login", base_path)).into_response() } Self::InternalServerError(err) => { tracing::error!("Application error: {:?}", err); (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() } Self::ForbiddenError(client_message) => { tracing::info!("Forbidden: {}", client_message); (StatusCode::FORBIDDEN, client_message).into_response() } Self::NotFoundError(client_message) => { tracing::info!("Not found: {}", client_message); (StatusCode::NOT_FOUND, client_message).into_response() } Self::TooManyRequestsError(client_message) => { // Debug level so that if this is from a runaway loop, it won't // overwhelm server logs tracing::debug!("Too many requests: {}", client_message); (StatusCode::TOO_MANY_REQUESTS, client_message).into_response() } Self::BadRequestError(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::AuthRedirect(info) => write!(f, "AuthRedirect: {:?}", info), AppError::InternalServerError(inner) => inner.fmt(f), AppError::ForbiddenError(client_message) => { write!(f, "ForbiddenError: {}", client_message) } AppError::NotFoundError(client_message) => { write!(f, "NotFoundError: {}", client_message) } AppError::BadRequestError(client_message) => { write!(f, "BadRequestError: {}", client_message) } AppError::TooManyRequestsError(client_message) => { write!(f, "TooManyRequestsError: {}", client_message) } } } }