use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use serde_json::json; pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum AppError { #[error("Not found: {0}")] NotFound(String), #[error("Unauthorized: {0}")] #[allow(dead_code)] Unauthorized(String), #[error("Bad request: {0}")] BadRequest(String), #[error("Forbidden")] Forbidden, #[error("Database error: {0}")] Database(#[from] sqlx::Error), #[error("S3 error: {0}")] S3(String), #[error("Internal error: {0}")] Internal(String), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, message) = match &self { AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()), AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg.clone()), AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()), AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden".to_string()), AppError::Database(e) => { tracing::error!("Database error: {e}"); ( StatusCode::INTERNAL_SERVER_ERROR, "Database error".to_string(), ) } AppError::S3(msg) => { tracing::error!("S3 error: {msg}"); (StatusCode::INTERNAL_SERVER_ERROR, "Storage error".to_string()) } AppError::Internal(msg) => { tracing::error!("Internal error: {msg}"); ( StatusCode::INTERNAL_SERVER_ERROR, "Internal error".to_string(), ) } }; let body = axum::Json(json!({ "error": message })); (status, body).into_response() } }