Skip to main content

sz_rust_http_facade/
error.rs

1//! 错误体系 — BaseException + 错误码映射
2//!
3//! 对齐 PHP `app\common\exception\BaseException`。
4//!
5//! ## PHP 错误码(从 PHP 后端代码提取)
6//!
7//! | code | 含义 | PHP 使用场景 |
8//! |------|------|-------------|
9//! | `1` | 成功 | `renderSuccess` 默认 |
10//! | `0` | 失败 | `renderError` 默认 / `BaseException` 默认 |
11//! | `-1` | 未登录/参数错误 | `not_login` / `缺少必要的参数` / `密钥不准确` |
12//! | `-2` | 用户不存在/未绑定 | `没有找到用户信息` / `请先绑定,员工信息` |
13//! | `-3` | 用户已禁用/已离职 | `员工信息待审核` / `您已离职` |
14//! | `403` | 无权限 |(Rust 扩展) |
15//! | `404` | 资源不存在 |(Rust 扩展) |
16//! | `422` | 验证失败 |(Rust 扩展) |
17//! | `413` | 请求体过大 |(Rust 扩展) |
18//! | `500` | 数据库错误 |(Rust 扩展) |
19//!
20//! ## JSON 响应格式
21//!
22//! ```json
23//! { "code": <code>, "msg": "<msg>", "data": {} }
24//! ```
25
26use serde::Serialize;
27use thiserror::Error;
28
29/// 错误码枚举(对齐 PHP BaseException 的 code 字段)
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
31#[repr(i32)]
32pub enum ErrorCode {
33    /// 成功(PHP renderSuccess 默认)
34    Success = 1,
35    /// 失败(PHP renderError 默认 / BaseException 默认)
36    Failed = 0,
37    /// 未登录/参数错误(PHP not_login / 缺少必要的参数 / 密钥不准确)
38    NotLogin = -1,
39    /// 用户不存在/未绑定(PHP 没有找到用户信息 / 请先绑定,员工信息)
40    UserNotFound = -2,
41    /// 用户已禁用/已离职/待审核(PHP 员工信息待审核 / 您已离职)
42    UserDisabled = -3,
43    /// 无权限(Rust 扩展,HTTP 403)
44    Forbidden = 403,
45    /// 资源不存在(Rust 扩展,HTTP 404)
46    NotFound = 404,
47    /// 验证失败(Rust 扩展,HTTP 422)
48    ValidateFailed = 422,
49    /// 请求体过大(Rust 扩展,HTTP 413)
50    PayloadTooLarge = 413,
51    /// 数据库错误(Rust 扩展,HTTP 500)
52    DbError = 500,
53}
54
55impl ErrorCode {
56    /// 转为 i32(对齐 PHP code 字段)
57    pub fn as_i32(self) -> i32 {
58        self as i32
59    }
60
61    /// 对应的 HTTP 状态码
62    pub fn http_status(self) -> u16 {
63        match self {
64            ErrorCode::Success => 200,
65            ErrorCode::Failed => 200,
66            ErrorCode::NotLogin => 401,
67            ErrorCode::UserNotFound => 401,
68            ErrorCode::UserDisabled => 403,
69            ErrorCode::Forbidden => 403,
70            ErrorCode::NotFound => 404,
71            ErrorCode::ValidateFailed => 422,
72            ErrorCode::PayloadTooLarge => 413,
73            ErrorCode::DbError => 500,
74        }
75    }
76}
77
78impl From<i32> for ErrorCode {
79    fn from(code: i32) -> Self {
80        match code {
81            1 => ErrorCode::Success,
82            0 => ErrorCode::Failed,
83            -1 => ErrorCode::NotLogin,
84            -2 => ErrorCode::UserNotFound,
85            -3 => ErrorCode::UserDisabled,
86            403 => ErrorCode::Forbidden,
87            404 => ErrorCode::NotFound,
88            422 => ErrorCode::ValidateFailed,
89            413 => ErrorCode::PayloadTooLarge,
90            500 => ErrorCode::DbError,
91            _ => ErrorCode::Failed,
92        }
93    }
94}
95
96/// BaseException — 对齐 PHP `app\common\exception\BaseException`
97///
98/// PHP 原始实现:
99/// ```php
100/// class BaseException extends Exception {
101///     public $code = 0;
102///     public $message = 'invalid parameters';
103///     public function __construct($params = []) {
104///         if (array_key_exists('code', $params)) { $this->code = $params['code']; }
105///         if (array_key_exists('msg', $params)) { $this->message = $params['msg']; }
106///     }
107/// }
108/// ```
109#[derive(Debug, Clone, Error)]
110#[error("[{code}] {msg}")]
111pub struct BaseException {
112    /// 错误码(对齐 PHP `$code`)
113    pub code: i32,
114    /// 错误消息(对齐 PHP `$message`,PHP 用 `msg` 键传入)
115    pub msg: String,
116    /// 本地化消息键(i18n 翻译用,默认 None 表示 msg 已是最终文案)
117    ///
118    /// 设置后由上层(如 sz-rust-mvc-facade 的 `i18n_error::localize_exception`)
119    /// 通过 i18n 模块翻译为指定语言文案。
120    pub message_key: Option<String>,
121}
122
123impl BaseException {
124    /// 创建 BaseException(对齐 PHP `new BaseException(['code' => x, 'msg' => y])`)
125    pub fn new(code: ErrorCode, msg: impl Into<String>) -> Self {
126        Self {
127            code: code.as_i32(),
128            msg: msg.into(),
129            message_key: None,
130        }
131    }
132
133    /// 设置本地化消息键(i18n 翻译用)
134    pub fn with_message_key(mut self, key: impl Into<String>) -> Self {
135        self.message_key = Some(key.into());
136        self
137    }
138
139    /// 获取本地化消息键(无则 None)
140    pub fn message_key(&self) -> Option<&str> {
141        self.message_key.as_deref()
142    }
143
144    /// 未登录快捷构造(对齐 PHP `throw new BaseException(['code' => -1, 'msg' => 'not_login'])`)
145    pub fn not_login(msg: impl Into<String>) -> Self {
146        Self::new(ErrorCode::NotLogin, msg)
147    }
148
149    /// 用户不存在快捷构造(对齐 PHP `throw new BaseException(['msg' => '没有找到用户信息', 'code' => -2])`)
150    pub fn user_not_found(msg: impl Into<String>) -> Self {
151        Self::new(ErrorCode::UserNotFound, msg)
152    }
153
154    /// 用户已禁用快捷构造(对齐 PHP `throw new BaseException(['msg' => '您已离职', 'code' => -3])`)
155    pub fn user_disabled(msg: impl Into<String>) -> Self {
156        Self::new(ErrorCode::UserDisabled, msg)
157    }
158
159    /// 失败快捷构造(对齐 PHP `renderError('error')`)
160    pub fn failed(msg: impl Into<String>) -> Self {
161        Self::new(ErrorCode::Failed, msg)
162    }
163
164    /// 无权限快捷构造
165    pub fn forbidden(msg: impl Into<String>) -> Self {
166        Self::new(ErrorCode::Forbidden, msg)
167    }
168
169    /// 资源不存在快捷构造
170    pub fn not_found(msg: impl Into<String>) -> Self {
171        Self::new(ErrorCode::NotFound, msg)
172    }
173
174    /// 验证失败快捷构造
175    pub fn validate_failed(msg: impl Into<String>) -> Self {
176        Self::new(ErrorCode::ValidateFailed, msg)
177    }
178
179    /// 数据库错误快捷构造
180    pub fn db_error(msg: impl Into<String>) -> Self {
181        Self::new(ErrorCode::DbError, msg)
182    }
183
184    /// 请求体过大快捷构造(HTTP 413)
185    pub fn payload_too_large(msg: impl Into<String>) -> Self {
186        Self::new(ErrorCode::PayloadTooLarge, msg)
187    }
188
189    /// 转为 JSON 响应(对齐 PHP `renderJson(code, msg, data)`)
190    pub fn to_json(&self) -> serde_json::Value {
191        serde_json::json!({
192            "code": self.code,
193            "msg": self.msg,
194            "data": {}
195        })
196    }
197}
198
199impl Default for BaseException {
200    fn default() -> Self {
201        Self {
202            code: ErrorCode::Failed.as_i32(),
203            msg: "invalid parameters".to_string(),
204            message_key: None,
205        }
206    }
207}
208
209// ============================================================================
210// 单元测试
211// ============================================================================
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    /// 测试错误码值与 PHP 一一对应
218    #[test]
219    fn test_error_code_values() {
220        assert_eq!(ErrorCode::Success.as_i32(), 1);
221        assert_eq!(ErrorCode::Failed.as_i32(), 0);
222        assert_eq!(ErrorCode::NotLogin.as_i32(), -1);
223        assert_eq!(ErrorCode::UserNotFound.as_i32(), -2);
224        assert_eq!(ErrorCode::UserDisabled.as_i32(), -3);
225        assert_eq!(ErrorCode::Forbidden.as_i32(), 403);
226        assert_eq!(ErrorCode::NotFound.as_i32(), 404);
227        assert_eq!(ErrorCode::ValidateFailed.as_i32(), 422);
228        assert_eq!(ErrorCode::DbError.as_i32(), 500);
229    }
230
231    /// 测试 i32 → ErrorCode 转换
232    #[test]
233    fn test_from_i32() {
234        assert_eq!(ErrorCode::from(1), ErrorCode::Success);
235        assert_eq!(ErrorCode::from(0), ErrorCode::Failed);
236        assert_eq!(ErrorCode::from(-1), ErrorCode::NotLogin);
237        assert_eq!(ErrorCode::from(-2), ErrorCode::UserNotFound);
238        assert_eq!(ErrorCode::from(-3), ErrorCode::UserDisabled);
239        assert_eq!(ErrorCode::from(999), ErrorCode::Failed); // 未知码默认 Failed
240    }
241
242    /// 测试 HTTP 状态码映射
243    #[test]
244    fn test_http_status() {
245        assert_eq!(ErrorCode::Success.http_status(), 200);
246        assert_eq!(ErrorCode::Failed.http_status(), 200);
247        assert_eq!(ErrorCode::NotLogin.http_status(), 401);
248        assert_eq!(ErrorCode::UserNotFound.http_status(), 401);
249        assert_eq!(ErrorCode::UserDisabled.http_status(), 403);
250        assert_eq!(ErrorCode::Forbidden.http_status(), 403);
251        assert_eq!(ErrorCode::NotFound.http_status(), 404);
252        assert_eq!(ErrorCode::ValidateFailed.http_status(), 422);
253        assert_eq!(ErrorCode::DbError.http_status(), 500);
254    }
255
256    /// 测试 BaseException 默认值(对齐 PHP `code=0, message='invalid parameters'`)
257    #[test]
258    fn test_default() {
259        let ex = BaseException::default();
260        assert_eq!(ex.code, 0);
261        assert_eq!(ex.msg, "invalid parameters");
262    }
263
264    /// 测试 not_login 快捷构造(对齐 PHP `code=-1, msg='not_login'`)
265    #[test]
266    fn test_not_login() {
267        let ex = BaseException::not_login("not_login");
268        assert_eq!(ex.code, -1);
269        assert_eq!(ex.msg, "not_login");
270    }
271
272    /// 测试 user_not_found 快捷构造(对齐 PHP `code=-2, msg='没有找到用户信息'`)
273    #[test]
274    fn test_user_not_found() {
275        let ex = BaseException::user_not_found("没有找到用户信息");
276        assert_eq!(ex.code, -2);
277        assert_eq!(ex.msg, "没有找到用户信息");
278    }
279
280    /// 测试 user_disabled 快捷构造(对齐 PHP `code=-3, msg='您已离职'`)
281    #[test]
282    fn test_user_disabled() {
283        let ex = BaseException::user_disabled("您已离职,无权使用本系统!");
284        assert_eq!(ex.code, -3);
285        assert_eq!(ex.msg, "您已离职,无权使用本系统!");
286    }
287
288    /// 测试 failed 快捷构造(对齐 PHP `renderError('error')` → `code=0`)
289    #[test]
290    fn test_failed() {
291        let ex = BaseException::failed("操作失败");
292        assert_eq!(ex.code, 0);
293        assert_eq!(ex.msg, "操作失败");
294    }
295
296    /// 测试 to_json(对齐 PHP `renderJson(code, msg, data)`)
297    #[test]
298    fn test_to_json() {
299        let ex = BaseException::not_login("not_login");
300        let json = ex.to_json();
301        assert_eq!(json["code"], -1);
302        assert_eq!(json["msg"], "not_login");
303        assert_eq!(json["data"], serde_json::json!({}));
304    }
305
306    /// 测试 Display trait
307    #[test]
308    fn test_display() {
309        let ex = BaseException::not_login("not_login");
310        assert_eq!(format!("{}", ex), "[-1] not_login");
311    }
312
313    /// 测试从 PHP 场景提取的错误码全覆盖
314    /// PHP 代码中实际使用的错误码:1, 0, -1, -2, -3
315    #[test]
316    fn test_php_error_codes_coverage() {
317        // PHP renderSuccess → code=1
318        assert_eq!(ErrorCode::Success.as_i32(), 1);
319        // PHP renderError → code=0
320        assert_eq!(ErrorCode::Failed.as_i32(), 0);
321        // PHP not_login → code=-1
322        assert_eq!(ErrorCode::NotLogin.as_i32(), -1);
323        // PHP 没有找到用户信息 → code=-2
324        assert_eq!(ErrorCode::UserNotFound.as_i32(), -2);
325        // PHP 您已离职 → code=-3
326        assert_eq!(ErrorCode::UserDisabled.as_i32(), -3);
327    }
328}