Skip to main content

tdm_server_rust/common/
result.rs

1//! API 统一响应格式 (Result Body)
2//!
3//! 定义 `{code, msg, data}` 三元组的统一响应体。
4//! 对齐 Java `Result<T>`,所有 API 端点均使用此格式返回。
5//!
6//! # 示例
7//!
8//! ```
9//! use tdm_server_rust::common::{ResultBody, ErrorCode};
10//!
11//! // 成功响应(无数据)
12//! let res = ResultBody::<()>::success();
13//! assert_eq!(res.code, 200);
14//! assert_eq!(res.msg, "成功!");
15//! assert!(res.data.is_none());
16//!
17//! // 成功响应(带数据)
18//! let res = ResultBody::success_data(vec![1, 2, 3]);
19//! assert_eq!(res.code, 200);
20//! assert_eq!(res.data, Some(vec![1, 2, 3]));
21//!
22//! // 错误响应
23//! let res = ResultBody::<()>::error("操作失败喵");
24//! assert_eq!(res.code, 500);
25//!
26//! // 指定错误码
27//! let res = ResultBody::<()>::error_code(ErrorCode::LOGIN_REQUIRED, "请登录");
28//! assert_eq!(res.code, 401);
29//! ```
30
31use super::error_code::ErrorCode;
32use axum::{
33    http::{header, StatusCode},
34    response::{IntoResponse, Response},
35};
36use crate::utils::fast_json;
37use serde::Serialize;
38
39/// API 统一响应体
40///
41/// 所有 HTTP API 的响应均包装为此格式:
42///
43/// ```json
44/// {
45///   "code": 200,
46///   "msg": "成功!",
47///   "data": { ... }
48/// }
49/// ```
50///
51/// # 构造方法
52///
53/// | 方法 | code | 用途 |
54/// |------|------|------|
55/// | [`success()`](Self::success) | 200 | 无数据成功响应 |
56/// | [`success_data()`](Self::success_data) | 200 | 带数据成功响应 |
57/// | [`error()`](Self::error) | 500 | 通用失败响应 |
58/// | [`error_code()`](Self::error_code) | 自定义 | 指定错误码的失败响应 |
59///
60/// # Panics
61///
62/// 本结构体及其方法均不 panic。序列化失败时通过 [`IntoResponse`]
63/// 优雅降级为 500 JSON 错误响应。
64///
65/// # 辅助函数
66///
67/// - [`permission_denied_response()`]: 权限不足 403
68/// - [`download_forbidden()`]: 下载无权限 403
69/// - [`download_failed()`]: 下载失败 500
70/// - [`json_response()`]: 任意可序列化类型转 JSON 响应
71#[derive(Debug, Clone, Serialize)]
72pub struct ResultBody<T> {
73    /// 响应码,200 成功
74    pub code: i32,
75    /// 响应消息
76    pub msg: String,
77    /// 业务数据
78    pub data: Option<T>,
79}
80
81impl<T: Serialize> ResultBody<T> {
82    /// 构建无数据的成功响应。
83    ///
84    /// # 返回值
85    ///
86    /// 返回 `code=200, msg="成功!", data=None` 的 [`ResultBody`]。
87    ///
88    /// # 示例
89    ///
90    /// ```
91    /// use tdm_server_rust::common::ResultBody;
92    /// let res: ResultBody<()> = ResultBody::success();
93    /// assert_eq!(res.code, 200);
94    /// ```
95    pub fn success() -> Self {
96        Self {
97            code: ErrorCode::SUCCESS,
98            msg: "成功!".to_string(),
99            data: None,
100        }
101    }
102
103    /// 构建带数据的成功响应。
104    ///
105    /// # 返回值
106    ///
107    /// 返回 `code=200, msg="成功!", data=Some(data)` 的 [`ResultBody`]。
108    ///
109    /// # 示例
110    ///
111    /// ```
112    /// use tdm_server_rust::common::ResultBody;
113    /// let res = ResultBody::success_data(42);
114    /// assert_eq!(res.data, Some(42));
115    /// ```
116    pub fn success_data(data: T) -> Self {
117        Self {
118            code: ErrorCode::SUCCESS,
119            msg: "成功!".to_string(),
120            data: Some(data),
121        }
122    }
123
124    /// 构建通用失败响应(错误码 500)。
125    ///
126    /// # 返回值
127    ///
128    /// 返回 `code=500, msg=<传入消息>, data=None` 的 [`ResultBody<()>`]。
129    pub fn error(msg: impl Into<String>) -> ResultBody<()> {
130        ResultBody {
131            code: ErrorCode::SYSTEM_ERROR,
132            msg: msg.into(),
133            data: None,
134        }
135    }
136
137    /// 构建指定错误码的失败响应。
138    ///
139    /// # 参数
140    ///
141    /// - `code`: 业务错误码,参见 [`ErrorCode`] 常量
142    /// - `msg`: 错误描述消息
143    ///
144    /// # 返回值
145    ///
146    /// 返回 `code=<指定值>, msg=<传入消息>, data=None` 的 [`ResultBody<()>`]。
147    pub fn error_code(code: i32, msg: impl Into<String>) -> ResultBody<()> {
148        ResultBody {
149            code,
150            msg: msg.into(),
151            data: None,
152        }
153    }
154}
155
156/// 将 [`ResultBody`] 转为 axum HTTP 响应。
157///
158/// Content-Type 对齐 Java: `application/json;charset=UTF-8`。
159///
160/// # Errors
161///
162/// JSON 序列化失败时返回 `500 {"code":500,"msg":"JSON 序列化失败: ...","data":null}` 纯文本降级。
163/// 此错误不应在正常业务中出现,仅当 `T` 的 `Serialize` 实现有 bug 才会触发。
164impl<T: Serialize> IntoResponse for ResultBody<T> {
165    fn into_response(self) -> Response {
166        match fast_json::to_vec(&self) {
167            Ok(bytes) => (
168                StatusCode::OK,
169                [(header::CONTENT_TYPE, "application/json;charset=UTF-8")],
170                bytes,
171            )
172                .into_response(),
173            Err(e) => (
174                StatusCode::INTERNAL_SERVER_ERROR,
175                [(header::CONTENT_TYPE, "application/json;charset=UTF-8")],
176                format!(r#"{{"code":500,"msg":"JSON 序列化失败: {e}","data":null}}"#),
177            )
178                .into_response(),
179        }
180    }
181}
182
183/// 权限不足 403 JSON 响应。
184///
185/// 绕过 [`ResultBody`] 直接返回 JSON:
186///
187/// ```json
188/// {"code": 403, "msg": "权限不足"}
189/// ```
190///
191/// 用于中间件拦截无权限请求的场景。
192///
193/// # 返回值
194///
195/// 返回 HTTP 403,Content-Type `application/json;charset=UTF-8`。
196pub fn permission_denied_response() -> Response {
197    (
198        StatusCode::FORBIDDEN,
199        [(header::CONTENT_TYPE, "application/json;charset=UTF-8")],
200        r#"{"code": 403, "msg": "权限不足"}"#,
201    )
202        .into_response()
203}
204
205/// 下载无权限 403 纯文本响应。
206///
207/// 返回纯文本而非 JSON,避免浏览器 blob 下载时将 JSON 当作文件保存。
208///
209/// # 返回值
210///
211/// 返回 HTTP 403,body 为传入消息字符串。
212pub fn download_forbidden(msg: impl Into<String>) -> Response {
213    (StatusCode::FORBIDDEN, msg.into()).into_response()
214}
215
216/// 下载失败 500 纯文本响应。
217///
218/// 对齐 Java `RuntimeException`:返回纯文本而非 JSON,
219/// 避免浏览器 blob 下载时将 JSON 保存为文件。
220///
221/// # 返回值
222///
223/// 返回 HTTP 500,body 为传入消息字符串。
224pub fn download_failed(msg: impl Into<String>) -> Response {
225    (StatusCode::INTERNAL_SERVER_ERROR, msg.into()).into_response()
226}
227
228/// 将任意可序列化类型转为 JSON HTTP 响应。
229///
230/// 用于非标准返回格式的场景(如 OSS 错误)。
231///
232/// # 返回值
233///
234/// 成功时返回 HTTP 200 + JSON body。
235///
236/// # Errors
237///
238/// 序列化失败时返回 HTTP 500 + 错误信息纯文本。
239pub fn json_response<T: Serialize>(value: &T) -> Response {
240    match fast_json::to_vec(value) {
241        Ok(bytes) => (
242            StatusCode::OK,
243            [(header::CONTENT_TYPE, "application/json;charset=UTF-8")],
244            bytes,
245        )
246            .into_response(),
247        Err(e) => (
248            StatusCode::INTERNAL_SERVER_ERROR,
249            format!("JSON 序列化失败: {e}"),
250        )
251            .into_response(),
252    }
253}