use axum::{ http::StatusCode, response::{IntoResponse, Json, Response}, }; use serde_json::json; use std::fmt; #[derive(Debug)] pub enum AppError { DatabaseError(String), ValidationError(String), AuthenticationError(String), AuthorizationError(String), NotFoundError(String), ConflictError(String), InternalError(String), } impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AppError::DatabaseError(msg) => write!(f, "Database error: {}", msg), AppError::ValidationError(msg) => write!(f, "Validation error: {}", msg), AppError::AuthenticationError(msg) => write!(f, "Authentication error: {}", msg), AppError::AuthorizationError(msg) => write!(f, "Authorization error: {}", msg), AppError::NotFoundError(msg) => write!(f, "Not found: {}", msg), AppError::ConflictError(msg) => write!(f, "Conflict: {}", msg), AppError::InternalError(msg) => write!(f, "Internal error: {}", msg), } } } impl std::error::Error for AppError {} impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error_message) = match self { AppError::DatabaseError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"), AppError::ValidationError(ref msg) => (StatusCode::BAD_REQUEST, msg.as_str()), AppError::AuthenticationError(ref msg) => (StatusCode::UNAUTHORIZED, msg.as_str()), AppError::AuthorizationError(ref msg) => (StatusCode::FORBIDDEN, msg.as_str()), AppError::NotFoundError(ref msg) => (StatusCode::NOT_FOUND, msg.as_str()), AppError::ConflictError(ref msg) => (StatusCode::CONFLICT, msg.as_str()), AppError::InternalError(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") } }; let body = Json(json!({ "error": error_message })); (status, body).into_response() } } impl From for AppError { fn from(err: anyhow::Error) -> Self { if let Some(sqlx_err) = err.downcast_ref::() { match sqlx_err { sqlx::Error::RowNotFound => { AppError::NotFoundError("Resource not found".to_string()) } sqlx::Error::Database(db_err) => { if db_err.message().contains("UNIQUE constraint failed") { AppError::ConflictError("Resource already exists".to_string()) } else { AppError::DatabaseError(db_err.message().to_string()) } } _ => AppError::DatabaseError("Database operation failed".to_string()), } } else { AppError::InternalError(err.to_string()) } } } impl From for AppError { fn from(_: bcrypt::BcryptError) -> Self { AppError::InternalError("Password hashing error".to_string()) } } impl From for AppError { fn from(err: sqlx::Error) -> Self { match err { sqlx::Error::RowNotFound => AppError::NotFoundError("Resource not found".to_string()), sqlx::Error::Database(db_err) => { if db_err.message().contains("UNIQUE constraint failed") { AppError::ConflictError("Resource already exists".to_string()) } else { AppError::DatabaseError(db_err.message().to_string()) } } _ => AppError::DatabaseError("Database operation failed".to_string()), } } } impl From for AppError { fn from(err: std::io::Error) -> Self { AppError::InternalError(format!("IO error: {}", err)) } } pub type AppResult = Result; pub fn validation_error(msg: &str) -> AppError { AppError::ValidationError(msg.to_string()) } pub fn auth_error(msg: &str) -> AppError { AppError::AuthenticationError(msg.to_string()) } pub fn forbidden_error(msg: &str) -> AppError { AppError::AuthorizationError(msg.to_string()) } pub fn not_found_error(msg: &str) -> AppError { AppError::NotFoundError(msg.to_string()) } pub fn conflict_error(msg: &str) -> AppError { AppError::ConflictError(msg.to_string()) } pub fn internal_error(msg: &str) -> AppError { AppError::InternalError(msg.to_string()) } pub fn success_response(data: T) -> Json where T: serde::Serialize, { Json(data) } pub fn success_message(msg: &str) -> Json { Json(json!({ "message": msg })) }