use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; use serde_json::json; use tracing::error; #[derive(Debug, thiserror::Error)] pub enum AppError { #[error("Not found: {0}")] NotFound(String), #[error("Unauthorized: {0}")] Unauthorized(String), #[error("Bad request: {0}")] BadRequest(String), #[error("Forbidden: {0}")] Forbidden(String), #[error("Conflict: {0}")] Conflict(String), #[error("{0}")] Validation(String), #[error("Internal error")] Internal, #[error(transparent)] Database(#[from] sqlx::Error), #[error(transparent)] Jwt(#[from] jsonwebtoken::errors::Error), #[error(transparent)] Io(#[from] std::io::Error), #[error("Argon2 error")] #[allow(dead_code)] Argon, #[error(transparent)] Reqwest(#[from] reqwest::Error), } impl IntoResponse for AppError { fn into_response(self) -> Response { error!("AppError: {self:?}"); let (status, message) = match &self { AppError::NotFound(m) => (StatusCode::NOT_FOUND, m.clone()), AppError::Unauthorized(m) => (StatusCode::UNAUTHORIZED, m.clone()), AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m.clone()), AppError::Forbidden(m) => (StatusCode::FORBIDDEN, m.clone()), AppError::Conflict(m) => (StatusCode::CONFLICT, m.clone()), AppError::Validation(m) => (StatusCode::UNPROCESSABLE_ENTITY, m.clone()), AppError::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into()), AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into()), AppError::Jwt(_) => (StatusCode::UNAUTHORIZED, "Invalid token".into()), AppError::Io(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into()), AppError::Argon => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into()), AppError::Reqwest(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into()), }; (status, Json(json!({ "error": message }))).into_response() } } pub type Result = std::result::Result;