fbc_starter/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde_json::json;
7use thiserror::Error;
8
9/// 应用错误类型
10#[derive(Error, Debug)]
11pub enum AppError {
12    #[error("内部服务器错误: {0}")]
13    Internal(#[from] anyhow::Error),
14
15    #[error("未找到资源")]
16    NotFound,
17
18    #[error("未授权访问")]
19    Unauthorized,
20
21    #[error("禁止访问")]
22    Forbidden,
23
24    #[error("请求参数错误: {0}")]
25    BadRequest(String),
26
27    #[error("配置错误: {0}")]
28    Config(#[from] config::ConfigError),
29
30    /// 数据库错误(需要启用 mysql/postgres/sqlite 任一特性)
31    #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
32    #[error("数据库错误: {0}")]
33    Database(#[from] sqlx::Error),
34
35    /// 数据库连接池未初始化错误
36    #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
37    #[error("数据库连接池未初始化")]
38    DatabaseNotInitialized,
39
40    /// Redis 错误(需要启用 redis 特性)
41    #[cfg(feature = "redis")]
42    #[error("Redis 错误: {0}")]
43    Redis(#[from] redis::RedisError),
44
45    /// Redis 客户端未初始化错误
46    #[cfg(feature = "redis")]
47    #[error("Redis 客户端未初始化")]
48    RedisNotInitialized,
49
50    /// 业务错误
51    ///
52    /// # 参数
53    /// - `status`: HTTP 状态码
54    /// - `message`: 错误消息
55    #[error("业务错误: {1}")]
56    BizError(StatusCode, String),
57
58    /// 通用错误
59    ///
60    /// # 参数
61    /// - `status`: HTTP 状态码
62    /// - `message`: 错误消息
63    #[error("通用错误: {1}")]
64    CommonError(StatusCode, String),
65
66    /// 自定义错误
67    ///
68    /// # 参数
69    /// - `status`: HTTP 状态码
70    /// - `message`: 错误消息
71    #[error("自定义错误: {1}")]
72    CustomerError(StatusCode, String),
73}
74
75/// 应用结果类型
76pub type AppResult<T> = Result<T, AppError>;
77
78impl AppError {
79    /// 创建业务错误
80    ///
81    /// # 参数
82    /// - `status`: HTTP 状态码
83    /// - `message`: 错误消息
84    ///
85    /// # 示例
86    /// ```rust,no_run
87    /// use fbc_starter::error::{AppError, AppResult};
88    /// use axum::http::StatusCode;
89    ///
90    /// fn example() -> AppResult<()> {
91    ///     Err(AppError::biz_error(StatusCode::BAD_REQUEST, "业务逻辑错误".to_string()))
92    /// }
93    /// ```
94    pub fn biz_error(status: StatusCode, message: String) -> Self {
95        Self::BizError(status, message)
96    }
97
98    /// 创建通用错误
99    ///
100    /// # 参数
101    /// - `status`: HTTP 状态码
102    /// - `message`: 错误消息
103    ///
104    /// # 示例
105    /// ```rust,no_run
106    /// use fbc_starter::error::{AppError, AppResult};
107    /// use axum::http::StatusCode;
108    ///
109    /// fn example() -> AppResult<()> {
110    ///     Err(AppError::common_error(StatusCode::INTERNAL_SERVER_ERROR, "通用错误".to_string()))
111    /// }
112    /// ```
113    pub fn common_error(status: StatusCode, message: String) -> Self {
114        Self::CommonError(status, message)
115    }
116
117    /// 创建自定义错误
118    ///
119    /// # 参数
120    /// - `status`: HTTP 状态码
121    /// - `message`: 错误消息
122    ///
123    /// # 示例
124    /// ```rust,no_run
125    /// use fbc_starter::error::{AppError, AppResult};
126    /// use axum::http::StatusCode;
127    ///
128    /// fn example() -> AppResult<()> {
129    ///     Err(AppError::customer_error(StatusCode::FORBIDDEN, "自定义错误".to_string()))
130    /// }
131    /// ```
132    pub fn customer_error(status: StatusCode, message: String) -> Self {
133        Self::CustomerError(status, message)
134    }
135}
136
137impl IntoResponse for AppError {
138    fn into_response(self) -> Response {
139        let (status, error_message) = match self {
140            AppError::NotFound => (StatusCode::NOT_FOUND, self.to_string()),
141            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
142            AppError::Forbidden => (StatusCode::FORBIDDEN, self.to_string()),
143            AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
144            AppError::Config(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
145            #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
146            AppError::Database(e) => {
147                tracing::error!("数据库错误: {:?}", e);
148                (
149                    StatusCode::INTERNAL_SERVER_ERROR,
150                    format!("数据库错误: {}", e),
151                )
152            }
153            #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
154            AppError::DatabaseNotInitialized => {
155                tracing::error!("数据库连接池未初始化");
156                (
157                    StatusCode::INTERNAL_SERVER_ERROR,
158                    "数据库连接池未初始化".to_string(),
159                )
160            }
161            #[cfg(feature = "redis")]
162            AppError::Redis(e) => {
163                tracing::error!("Redis 错误: {:?}", e);
164                (
165                    StatusCode::INTERNAL_SERVER_ERROR,
166                    format!("Redis 错误: {}", e),
167                )
168            }
169            #[cfg(feature = "redis")]
170            AppError::RedisNotInitialized => {
171                tracing::error!("Redis 客户端未初始化");
172                (
173                    StatusCode::INTERNAL_SERVER_ERROR,
174                    "Redis 客户端未初始化".to_string(),
175                )
176            }
177            AppError::Internal(e) => {
178                tracing::error!("内部错误: {:?}", e);
179                (
180                    StatusCode::INTERNAL_SERVER_ERROR,
181                    "内部服务器错误".to_string(),
182                )
183            }
184            AppError::BizError(status, msg) => {
185                tracing::warn!("业务错误: {} (状态码: {})", msg, status);
186                (status, msg)
187            }
188            AppError::CommonError(status, msg) => {
189                tracing::warn!("通用错误: {} (状态码: {})", msg, status);
190                (status, msg)
191            }
192            AppError::CustomerError(status, msg) => {
193                tracing::warn!("自定义错误: {} (状态码: {})", msg, status);
194                (status, msg)
195            }
196        };
197
198        let body = Json(json!({
199            "error": error_message,
200            "status": status.as_u16(),
201        }));
202
203        (status, body).into_response()
204    }
205}