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