use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use serde_json::json; use thiserror::Error; #[derive(Error, Debug)] pub enum AppError { #[error("Database error: {0}")] Database(#[from] sqlx::Error), #[error("Migration error: {0}")] Migration(#[from] sqlx::migrate::MigrateError), #[error("Validation error: {0}")] Validation(String), #[error("Authentication error: {0}")] Authentication(String), #[error("Authorization error: {0}")] Authorization(String), #[error("Not found: {0}")] NotFound(String), #[error("Conflict: {0}")] Conflict(String), #[error("External service error: {0}")] ExternalService(String), #[error("Configuration error: {0}")] Configuration(String), #[error("JSON serialization error: {0}")] Json(#[from] serde_json::Error), #[error("HTTP client error: {0}")] HttpClient(#[from] reqwest::Error), #[error("JWT error: {0}")] Jwt(#[from] jsonwebtoken::errors::Error), #[error("Internal server error: {0}")] Internal(String), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error_message, error_code) = match &self { AppError::Database(e) => { tracing::error!("Database error: {}", e); (StatusCode::INTERNAL_SERVER_ERROR, "Database error", "DATABASE_ERROR") } AppError::Migration(e) => { tracing::error!("Migration error: {}", e); (StatusCode::INTERNAL_SERVER_ERROR, "Migration error", "MIGRATION_ERROR") } AppError::Validation(msg) => { (StatusCode::BAD_REQUEST, msg.as_str(), "VALIDATION_ERROR") } AppError::Authentication(msg) => { (StatusCode::UNAUTHORIZED, msg.as_str(), "AUTHENTICATION_ERROR") } AppError::Authorization(msg) => { (StatusCode::FORBIDDEN, msg.as_str(), "AUTHORIZATION_ERROR") } AppError::NotFound(msg) => { (StatusCode::NOT_FOUND, msg.as_str(), "NOT_FOUND") } AppError::Conflict(msg) => { (StatusCode::CONFLICT, msg.as_str(), "CONFLICT") } AppError::ExternalService(msg) => { tracing::error!("External service error: {}", msg); (StatusCode::BAD_GATEWAY, "External service unavailable", "EXTERNAL_SERVICE_ERROR") } AppError::Configuration(msg) => { tracing::error!("Configuration error: {}", msg); (StatusCode::INTERNAL_SERVER_ERROR, "Configuration error", "CONFIGURATION_ERROR") } AppError::Json(e) => { tracing::error!("JSON error: {}", e); (StatusCode::BAD_REQUEST, "Invalid JSON", "JSON_ERROR") } AppError::HttpClient(e) => { tracing::error!("HTTP client error: {}", e); (StatusCode::BAD_GATEWAY, "External service error", "HTTP_CLIENT_ERROR") } AppError::Jwt(e) => { tracing::error!("JWT error: {}", e); (StatusCode::UNAUTHORIZED, "Invalid token", "JWT_ERROR") } AppError::Internal(msg) => { tracing::error!("Internal error: {}", msg); (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error", "INTERNAL_ERROR") } }; let body = Json(json!({ "error": { "code": error_code, "message": error_message, "timestamp": chrono::Utc::now().to_rfc3339() } })); (status, body).into_response() } } // Convenience methods for common errors impl AppError { pub fn validation>(msg: T) -> Self { Self::Validation(msg.into()) } pub fn authentication>(msg: T) -> Self { Self::Authentication(msg.into()) } pub fn authorization>(msg: T) -> Self { Self::Authorization(msg.into()) } pub fn not_found>(msg: T) -> Self { Self::NotFound(msg.into()) } pub fn conflict>(msg: T) -> Self { Self::Conflict(msg.into()) } pub fn internal>(msg: T) -> Self { Self::Internal(msg.into()) } } pub type AppResult = Result;