use axum::{ Json, http::StatusCode, response::{IntoResponse, Response}, }; use serde::Serialize; use utoipa::{IntoResponses, ToSchema}; #[derive(Debug, thiserror::Error)] pub enum ApiError { #[error("{0}")] BadRequest(String), #[error("authentication is required")] Unauthorized, #[error("resource not found")] NotFound, #[error("database has not been configured")] DatabaseUnavailable, #[error("{0}")] ServiceUnavailable(String), #[error("internal server error")] Internal, } /// API 错误响应。 #[derive(Serialize, ToSchema)] #[schema(example = json!({"error": "resource not found"}))] pub struct ErrorBody { /// 面向调用方的错误信息。 pub error: String, } /// 所有需要数据库和临时用户身份的业务接口都可能返回的公共错误。 #[allow(dead_code)] #[derive(IntoResponses)] pub enum CommonApiErrors { /// 参数格式或业务校验失败。 #[response( status = 400, description = "请求参数无效", example = json!({"error": "X-User-Id must be a UUID"}) )] BadRequest(ErrorBody), /// 缺少 `X-User-Id` 请求头。 #[response( status = 401, description = "缺少开发阶段身份请求头", example = json!({"error": "authentication is required"}) )] Unauthorized(ErrorBody), /// 服务未配置数据库连接。 #[response( status = 503, description = "未配置 DATABASE_URL", example = json!({"error": "database has not been configured"}) )] DatabaseUnavailable(ErrorBody), /// 数据库查询或服务内部处理失败。 #[response( status = 500, description = "服务内部错误", example = json!({"error": "internal server error"}) )] Internal(ErrorBody), } impl IntoResponse for ApiError { fn into_response(self) -> Response { let (status, message) = match self { Self::BadRequest(message) => (StatusCode::BAD_REQUEST, message), Self::Unauthorized => ( StatusCode::UNAUTHORIZED, "authentication is required".to_owned(), ), Self::NotFound => (StatusCode::NOT_FOUND, "resource not found".to_owned()), Self::DatabaseUnavailable => ( StatusCode::SERVICE_UNAVAILABLE, "database has not been configured".to_owned(), ), Self::ServiceUnavailable(message) => (StatusCode::SERVICE_UNAVAILABLE, message), Self::Internal => ( StatusCode::INTERNAL_SERVER_ERROR, "internal server error".to_owned(), ), }; (status, Json(ErrorBody { error: message })).into_response() } } impl From for ApiError { fn from(error: sqlx::Error) -> Self { tracing::error!(?error, "database query failed"); Self::Internal } }