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}