Skip to main content

sz_rust_core/
response.rs

1//! 响应模块 — renderJson/renderSuccess/renderError + ApiResponse
2//!
3//! 对齐 PHP `SzController::renderJson` / `renderSuccess` / `renderError`。
4//! 响应格式:`{ "code": 1, "msg": "", "data": {} }`(字段顺序 code→msg→data)。
5//!
6//! ## PHP 对齐
7//!
8//! | PHP 方法 | 行为 | Rust 等价 |
9//! |---------|------|-----------|
10//! | `renderJson($code, $msg, $data)` | 标准 JSON 响应 | [`ApiResponse::new`] + `ApiResponse::into_response` |
11//! | `renderSuccess($msg, $data)` | `code=1` 成功响应 | [`ApiResponse::success`](Rust 参数顺序:data, msg) |
12//! | `renderError($msg, $data)` | `code=0` 失败响应 | [`ApiResponse::error`] |
13//! | `renderError($msg, $data, $code)` | 自定义错误码 | [`ApiResponse::error_with_code`](Rust 参数顺序:code, msg, data) |
14//!
15//! ## 字段顺序
16//!
17//! 严格遵循 PHP `renderJson` 的字段顺序:`code → msg → data`。
18//! Rust 使用 `serde_json::Map`(`preserve_order` feature)来保证序列化顺序。
19//! `serde_json` 默认启用 `preserve_order`,依赖 `indexmap`。
20//!
21//! ## Content-Type
22//!
23//! 所有响应自动附带 `Content-Type: application/json; charset=utf-8`。
24//!
25//! ## HTTP 状态码
26//!
27//! 业务成功(`code=1`)和业务失败(`code=0`)都返回 HTTP 200(对齐 PHP 行为);
28//! 异常场景(500/404)由 1.9 错误处理模块处理。
29
30use axum::http::StatusCode;
31use axum::response::{IntoResponse, Response};
32use serde::Serialize;
33use serde_json::{Map, Value};
34
35/// 标准 API 响应结构体
36///
37/// 严格对齐 PHP `renderJson` 输出格式:`{code, msg, data}`,字段顺序固定。
38///
39/// ## 用法
40///
41/// ```ignore
42/// use sz_rust_core::response::ApiResponse;
43/// use serde_json::json;
44///
45/// // 成功响应
46/// let resp = ApiResponse::success(json!({"id": 1}), "ok");
47///
48/// // 错误响应
49/// let resp = ApiResponse::error("参数错误");
50///
51/// // 自定义 code
52/// let resp = ApiResponse::new(-1, "未登录", json!({}));
53/// ```
54#[derive(Debug, Clone)]
55pub struct ApiResponse {
56    /// 业务状态码(1=成功,0=失败,-1=未登录,与 PHP BaseException 对齐)
57    pub code: i32,
58    /// 业务消息
59    pub msg: String,
60    /// 业务数据
61    pub data: Value,
62}
63
64impl ApiResponse {
65    /// 创建新的 ApiResponse
66    pub fn new(code: i32, msg: impl Into<String>, data: Value) -> Self {
67        Self {
68            code,
69            msg: msg.into(),
70            data,
71        }
72    }
73
74    /// 创建成功响应(code=1)
75    ///
76    /// 对齐 PHP `renderSuccess($data = [], $msg = '')`。
77    pub fn success(data: Value, msg: impl Into<String>) -> Self {
78        Self::new(1, msg, data)
79    }
80
81    /// 创建成功响应(默认空 data + 空 msg)
82    pub fn success_empty() -> Self {
83        Self::success(Value::Object(Map::new()), "")
84    }
85
86    /// 创建错误响应(code=0)
87    ///
88    /// 对齐 PHP `renderError($msg = '', $data = [])`。
89    pub fn error(msg: impl Into<String>) -> Self {
90        Self::new(0, msg, Value::Object(Map::new()))
91    }
92
93    /// 创建带数据的错误响应(code=0)
94    pub fn error_with_data(msg: impl Into<String>, data: Value) -> Self {
95        Self::new(0, msg, data)
96    }
97
98    /// 创建带自定义错误码的错误响应
99    ///
100    /// 对齐 PHP `renderError($code, $msg, $data)`。
101    pub fn error_with_code(code: i32, msg: impl Into<String>, data: Value) -> Self {
102        Self::new(code, msg, data)
103    }
104
105    /// 序列化为 `serde_json::Value`(保证字段顺序 code → msg → data)
106    ///
107    /// 使用 `serde_json::Map`(启用 `preserve_order`)保证插入顺序。
108    pub fn to_value(&self) -> Value {
109        let mut map = Map::new();
110        map.insert("code".to_string(), Value::Number(self.code.into()));
111        map.insert("msg".to_string(), Value::String(self.msg.clone()));
112        map.insert("data".to_string(), self.data.clone());
113        Value::Object(map)
114    }
115
116    /// 序列化为 JSON 字符串
117    pub fn to_json_string(&self) -> String {
118        self.to_value().to_string()
119    }
120}
121
122impl Serialize for ApiResponse {
123    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124    where
125        S: serde::Serializer,
126    {
127        self.to_value().serialize(serializer)
128    }
129}
130
131/// 让 ApiResponse 可以直接作为 axum handler 返回值
132///
133/// 自动设置:
134/// - HTTP 状态码:200(无论业务 code 是 1 还是 0,HTTP 都是 200,对齐 PHP 行为)
135/// - Content-Type: `application/json; charset=utf-8`
136impl IntoResponse for ApiResponse {
137    fn into_response(self) -> Response {
138        let body = self.to_json_string();
139        (
140            StatusCode::OK,
141            [(
142                axum::http::header::CONTENT_TYPE,
143                "application/json; charset=utf-8",
144            )],
145            body,
146        )
147            .into_response()
148    }
149}
150
151/// 直接构建标准 JSON 响应(无需创建 ApiResponse 实例)
152///
153/// 对齐 PHP `renderJson($code, $msg, $data)`。
154#[tracing::instrument(skip(msg, data))]
155pub fn render_json(code: i32, msg: impl Into<String>, data: Value) -> Response {
156    ApiResponse::new(code, msg, data).into_response()
157}
158
159/// 构建成功响应
160///
161/// 对齐 PHP `renderSuccess($data, $msg)`。
162#[tracing::instrument(skip(data, msg))]
163pub fn render_success(data: Value, msg: impl Into<String>) -> Response {
164    ApiResponse::success(data, msg).into_response()
165}
166
167/// 构建错误响应
168///
169/// 对齐 PHP `renderError($msg, $data)`。
170#[tracing::instrument(skip(msg))]
171pub fn render_error(msg: impl Into<String>) -> Response {
172    ApiResponse::error(msg).into_response()
173}
174
175/// 构建带自定义错误码的错误响应
176///
177/// 对齐 PHP `renderError($code, $msg, $data)`。
178#[tracing::instrument(skip(msg, data))]
179pub fn render_error_with_code(code: i32, msg: impl Into<String>, data: Value) -> Response {
180    ApiResponse::error_with_code(code, msg, data).into_response()
181}
182
183// ============================================================================
184// 前后端分离 JSON 默认返回(项目主策略)
185//
186// 对齐 PHP ThinkPHP 6 `Dispatch::autoResponse()` 行为,但将 JSON 设为默认响应类型
187// (项目主策略:前后端分离)。PHP `autoResponse()` 根据 `$this->request->isJson()`
188// 判断响应类型:isJson → JSON,否则 → HTML(数组会被输出为字面量 "Array")。
189//
190// 本模块扩展三种策略:
191// 1. `DefaultResponseType::Json` — 项目主策略:默认返回 JSON(不渲染模板)
192// 2. `DefaultResponseType::Html` — 兜底场景:返回 HTML(模板渲染使用)
193// 3. `DefaultResponseType::Auto` — 对齐 PHP autoResponse:根据 Accept 头判断
194//
195// ## PHP 源码参考
196//
197// ```php
198// // vendor/topthink/framework/src/think/route/Dispatch.php:84-107
199// protected function autoResponse($data): Response
200// {
201//     if ($data instanceof Response) {
202//         $response = $data;
203//     } elseif ($data instanceof ResponseInterface) {
204//         $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode());
205//         foreach ($data->getHeaders() as $header => $values) {
206//             $response->header([$header => implode(", ", $values)]);
207//         }
208//     } elseif (!is_null($data)) {
209//         // 默认自动识别响应输出类型
210//         $type     = $this->request->isJson() ? 'json' : 'html';
211//         $response = Response::create($data, $type);
212//     } else {
213//         $data = ob_get_clean();
214//         $content  = false === $data ? '' : $data;
215//         $status   = '' === $content && $this->request->isJson() ? 204 : 200;
216//         $response = Response::create($content, 'html', $status);
217//     }
218//     return $response;
219// }
220// ```
221//
222// ```php
223// // vendor/topthink/framework/src/think/Request.php:1557-1562
224// public function isJson(): bool
225// {
226//     $acceptType = $this->type();
227//     return false !== strpos($acceptType, 'json');
228// }
229// ```
230// ============================================================================
231
232use axum::http::{header, HeaderMap};
233
234/// 默认响应类型策略(对齐 PHP `autoResponse` + 项目主策略扩展)
235///
236/// 项目主策略为前后端分离,因此默认使用 `Json`。`Auto` 严格对齐 PHP TP 6
237/// `autoResponse()` 的行为(根据 Accept 头判断)。`Html` 用于兜底场景
238/// (如模板渲染、PDF/Excel 导出)。
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
240pub enum DefaultResponseType {
241    /// 项目主策略:默认返回 JSON(Content-Type: application/json; charset=utf-8)
242    ///
243    /// 控制器返回 `Value`(数组/对象)时,自动序列化为 JSON 响应。
244    /// 不渲染模板,不检查 Accept 头。
245    #[default]
246    Json,
247
248    /// 兜底场景:返回 HTML(Content-Type: text/html; charset=utf-8)
249    ///
250    /// 用于模板渲染、PDF/Excel 导出等非 JSON 场景。
251    Html,
252
253    /// 对齐 PHP `autoResponse`:根据请求 `Accept` 头判断
254    ///
255    /// - Accept 含 `json` MIME → JSON 响应
256    /// - Accept 不含 `json` MIME → HTML 响应(数组输出字面量 "Array",对齐 PHP bug)
257    Auto,
258}
259
260impl DefaultResponseType {
261    /// 根据策略和请求数据生成响应
262    ///
263    /// # 参数
264    /// - `data`:响应数据(`Value::Object` / `Value::Array` / `Value::String` 等)
265    /// - `headers`:请求头(用于 `Auto` 策略判断 `Accept` 头)
266    ///
267    /// # 返回
268    /// - `Json` → `respond(data)`
269    /// - `Html` → `respond_html(data.to_string())`
270    /// - `Auto` → `auto_respond(data, headers)`
271    pub fn respond(&self, data: &Value, headers: &HeaderMap) -> Response {
272        match self {
273            DefaultResponseType::Json => respond(data),
274            DefaultResponseType::Html => respond_html(data.to_string()),
275            DefaultResponseType::Auto => auto_respond(data, headers),
276        }
277    }
278}
279
280/// 检查请求是否为 JSON 请求(对齐 PHP `Request::isJson()`)
281///
282/// PHP 逻辑:检查 `Accept` 请求头是否包含 `json` MIME 类型
283/// (如 `application/json`、`text/json`、`application/vnd.api+json`)。
284///
285/// # PHP 对齐
286///
287/// ```php
288/// // vendor/topthink/framework/src/think/Request.php:1557-1562
289/// public function isJson(): bool
290/// {
291///     $acceptType = $this->type();
292///     return false !== strpos($acceptType, 'json');
293/// }
294/// ```
295///
296/// # 参数
297///
298/// - `headers`:请求头
299///
300/// # 返回
301///
302/// - `true`:`Accept` 头存在且包含 `json` 子串
303/// - `false`:`Accept` 头不存在或不包含 `json` 子串
304pub fn is_json_request(headers: &HeaderMap) -> bool {
305    if let Some(accept) = headers.get(header::ACCEPT) {
306        if let Ok(accept_str) = accept.to_str() {
307            // 对齐 PHP `strpos($acceptType, 'json') !== false`
308            // PHP `type()` 方法从 Accept 头解析 MIME 类型,再检查是否包含 "json"
309            // Rust 简化为直接检查 Accept 头是否包含 "json" 子串
310            // (覆盖 application/json、text/json、application/vnd.api+json 等)
311            return accept_str.to_lowercase().contains("json");
312        }
313    }
314    false
315}
316
317/// 默认 JSON 响应(项目主策略:前后端分离)
318///
319/// 将任意 `Value` 序列化为 JSON 响应,Content-Type 为
320/// `application/json; charset=utf-8`,HTTP 状态码 200。
321///
322/// # 项目主策略
323///
324/// 项目采用前后端分离架构,所有 API 响应默认为 JSON 格式。
325/// 控制器方法可直接返回 `Value`,由本函数统一转换为 JSON 响应。
326///
327/// # 参数
328///
329/// - `data`:要序列化的数据(`Value::Object` / `Value::Array` / `Value::String` 等)
330///
331/// # 返回
332///
333/// `Response`,HTTP 200,Content-Type: application/json; charset=utf-8
334#[tracing::instrument(skip(data))]
335pub fn respond(data: &Value) -> Response {
336    let body = data.to_string();
337    (
338        StatusCode::OK,
339        [(header::CONTENT_TYPE, "application/json; charset=utf-8")],
340        body,
341    )
342        .into_response()
343}
344
345/// HTML 响应(兜底场景:模板渲染、PDF/Excel 导出)
346///
347/// Content-Type 为 `text/html; charset=utf-8`,HTTP 状态码 200。
348///
349/// # 参数
350///
351/// - `content`:HTML 内容
352///
353/// # 返回
354///
355/// `Response`,HTTP 200,Content-Type: text/html; charset=utf-8
356#[tracing::instrument(skip(content))]
357pub fn respond_html(content: impl Into<String>) -> Response {
358    (
359        StatusCode::OK,
360        [(header::CONTENT_TYPE, "text/html; charset=utf-8")],
361        content.into(),
362    )
363        .into_response()
364}
365
366/// 纯文本响应(Content-Type: text/plain; charset=utf-8)
367///
368/// 用于调试、健康检查等非 JSON/HTML 场景。
369///
370/// # 参数
371///
372/// - `content`:文本内容
373///
374/// # 返回
375///
376/// `Response`,HTTP 200,Content-Type: text/plain; charset=utf-8
377#[tracing::instrument(skip(content))]
378pub fn respond_text(content: impl Into<String>) -> Response {
379    (
380        StatusCode::OK,
381        [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
382        content.into(),
383    )
384        .into_response()
385}
386
387/// 自动响应(对齐 PHP `Dispatch::autoResponse()`)
388///
389/// 严格对齐 PHP ThinkPHP 6 `autoResponse()` 行为:
390/// 1. 根据 `is_json_request(headers)` 判断响应类型
391/// 2. JSON 请求 → JSON 响应(`respond(data)`)
392/// 3. 非 JSON 请求 → HTML 响应
393///    - `Value::Array` / `Value::Object` → 字面量 `"Array"`(对齐 PHP bug)
394///    - `Value::String` → 字符串内容
395///    - 其他类型 → `data.to_string()`
396///
397/// # PHP bug 复刻说明
398///
399/// PHP `Response::create($data, 'html')` 在 `$data` 为数组时,会通过 `print` 输出
400/// 数组,导致输出字面量 `"Array"`。这是 PHP 的已知行为,本函数严格复刻此 bug
401/// 以保证 R5(PHP/Rust 行为对比)一致性。
402///
403/// # PHP 对齐
404///
405/// ```php
406/// // vendor/topthink/framework/src/think/route/Dispatch.php:96-97
407/// $type     = $this->request->isJson() ? 'json' : 'html';
408/// $response = Response::create($data, $type);
409/// ```
410///
411/// # 参数
412///
413/// - `data`:响应数据
414/// - `headers`:请求头(用于判断 `Accept` 头)
415///
416/// # 返回
417///
418/// - JSON 请求 → JSON 响应
419/// - 非 JSON 请求 → HTML 响应(数组输出字面量 `"Array"`)
420#[tracing::instrument(skip(data, headers))]
421pub fn auto_respond(data: &Value, headers: &HeaderMap) -> Response {
422    if is_json_request(headers) {
423        // 对齐 PHP: $type = 'json'
424        respond(data)
425    } else {
426        // 对齐 PHP: $type = 'html'
427        // PHP bug 复刻:数组/对象输出字面量 "Array"
428        let content = match data {
429            Value::Array(_) | Value::Object(_) => "Array".to_string(),
430            Value::String(s) => s.clone(),
431            Value::Null => String::new(),
432            _ => data.to_string(),
433        };
434        respond_html(content)
435    }
436}
437
438/// JSON 响应包装器(项目主策略:默认 JSON 返回)
439///
440/// 由于 Rust 孤儿规则限制,无法直接为 `serde_json::Value` 实现 `IntoResponse`。
441/// 本 newtype 包装 `Value`,使其可以直接作为 axum handler 返回值,
442/// 默认返回 JSON 响应(Content-Type: application/json; charset=utf-8)。
443///
444/// # 用法
445///
446/// ```ignore
447/// use sz_rust_core::response::JsonResponse;
448/// use serde_json::json;
449///
450/// async fn handler() -> JsonResponse {
451///     JsonResponse(json!({"id": 1, "name": "alice"}))
452/// }
453/// ```
454///
455/// 也可通过 `From<Value>` 转换:
456///
457/// ```ignore
458/// use sz_rust_core::response::JsonResponse;
459/// use serde_json::json;
460///
461/// async fn handler() -> JsonResponse {
462///     json!({"id": 1}).into()
463/// }
464/// ```
465///
466/// # 注意
467///
468/// 此类型始终返回 JSON 响应(项目主策略)。若需根据 Accept 头判断,
469/// 请使用 [`auto_respond`] 或 [`DefaultResponseType::Auto`]。
470#[derive(Debug, Clone)]
471pub struct JsonResponse(pub Value);
472
473impl From<Value> for JsonResponse {
474    fn from(v: Value) -> Self {
475        JsonResponse(v)
476    }
477}
478
479impl IntoResponse for JsonResponse {
480    fn into_response(self) -> Response {
481        respond(&self.0)
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488    use axum::body::Body;
489    use axum::http::{Method, Request};
490    use http_body_util::BodyExt;
491    use tower::ServiceExt;
492
493    // ====================================================================
494    // ApiResponse 单元测试
495    // ====================================================================
496
497    #[test]
498    fn test_api_response_new() {
499        let resp = ApiResponse::new(1, "ok", Value::Object(Map::new()));
500        assert_eq!(resp.code, 1);
501        assert_eq!(resp.msg, "ok");
502        assert!(resp.data.is_object());
503    }
504
505    #[test]
506    fn test_api_response_success() {
507        let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
508        assert_eq!(resp.code, 1);
509        assert_eq!(resp.msg, "ok");
510        assert_eq!(resp.data["id"], 1);
511    }
512
513    #[test]
514    fn test_api_response_success_empty() {
515        let resp = ApiResponse::success_empty();
516        assert_eq!(resp.code, 1);
517        assert_eq!(resp.msg, "");
518        assert!(resp.data.is_object());
519        assert!(resp.data.as_object().unwrap().is_empty());
520    }
521
522    #[test]
523    fn test_api_response_error() {
524        let resp = ApiResponse::error("参数错误");
525        assert_eq!(resp.code, 0);
526        assert_eq!(resp.msg, "参数错误");
527        assert!(resp.data.is_object());
528    }
529
530    #[test]
531    fn test_api_response_error_with_data() {
532        let resp = ApiResponse::error_with_data("失败", serde_json::json!({"field": "name"}));
533        assert_eq!(resp.code, 0);
534        assert_eq!(resp.msg, "失败");
535        assert_eq!(resp.data["field"], "name");
536    }
537
538    #[test]
539    fn test_api_response_error_with_code() {
540        let resp = ApiResponse::error_with_code(-1, "未登录", Value::Object(Map::new()));
541        assert_eq!(resp.code, -1);
542        assert_eq!(resp.msg, "未登录");
543    }
544
545    #[test]
546    fn test_api_response_to_value_field_order() {
547        let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1}));
548        let value = resp.to_value();
549        let obj = value.as_object().unwrap();
550
551        // 字段顺序必须是 code → msg → data
552        let keys: Vec<&String> = obj.keys().collect();
553        assert_eq!(keys, vec!["code", "msg", "data"]);
554    }
555
556    #[test]
557    fn test_api_response_to_value_content() {
558        let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1, "name": "alice"}));
559        let value = resp.to_value();
560        assert_eq!(value["code"], 1);
561        assert_eq!(value["msg"], "ok");
562        assert_eq!(value["data"]["id"], 1);
563        assert_eq!(value["data"]["name"], "alice");
564    }
565
566    #[test]
567    fn test_api_response_to_json_string() {
568        let resp = ApiResponse::new(1, "ok", serde_json::json!({}));
569        let json_str = resp.to_json_string();
570        // 字段顺序必须是 code → msg → data
571        let expected = r#"{"code":1,"msg":"ok","data":{}}"#;
572        assert_eq!(json_str, expected);
573    }
574
575    #[test]
576    fn test_api_response_to_json_string_with_data() {
577        let resp = ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok");
578        let json_str = resp.to_json_string();
579        let expected = r#"{"code":1,"msg":"ok","data":{"id":1,"name":"alice"}}"#;
580        assert_eq!(json_str, expected);
581    }
582
583    #[test]
584    fn test_api_response_serialize_via_serde() {
585        let resp = ApiResponse::new(0, "失败", Value::Object(Map::new()));
586        let json_str = serde_json::to_string(&resp).unwrap();
587        assert_eq!(json_str, r#"{"code":0,"msg":"失败","data":{}}"#);
588    }
589
590    #[test]
591    fn test_api_response_clone() {
592        let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
593        let cloned = resp.clone();
594        assert_eq!(cloned.code, resp.code);
595        assert_eq!(cloned.msg, resp.msg);
596        assert_eq!(cloned.data, resp.data);
597    }
598
599    #[test]
600    fn test_api_response_debug_format() {
601        let resp = ApiResponse::new(1, "ok", Value::Object(Map::new()));
602        let debug_str = format!("{resp:?}");
603        assert!(debug_str.contains("ApiResponse"));
604        assert!(debug_str.contains("code: 1"));
605        assert!(debug_str.contains("\"ok\""));
606    }
607
608    // ====================================================================
609    // 便捷函数测试
610    // ====================================================================
611
612    #[test]
613    fn test_render_json_returns_response() {
614        let resp = render_json(1, "ok", serde_json::json!({}));
615        assert_eq!(resp.status(), StatusCode::OK);
616        assert_eq!(
617            resp.headers().get("content-type").unwrap(),
618            "application/json; charset=utf-8"
619        );
620    }
621
622    #[test]
623    fn test_render_success_returns_response() {
624        let resp = render_success(serde_json::json!({"id": 1}), "ok");
625        assert_eq!(resp.status(), StatusCode::OK);
626    }
627
628    #[test]
629    fn test_render_error_returns_response() {
630        let resp = render_error("参数错误");
631        assert_eq!(resp.status(), StatusCode::OK); // 业务错误 HTTP 仍 200
632    }
633
634    #[test]
635    fn test_render_error_with_code_returns_response() {
636        let resp = render_error_with_code(-1, "未登录", serde_json::json!({}));
637        assert_eq!(resp.status(), StatusCode::OK);
638    }
639
640    // ====================================================================
641    // 集成测试:通过 axum Router 验证完整响应
642    // ====================================================================
643
644    #[tokio::test]
645    async fn test_api_response_as_handler_return() {
646        async fn handler() -> ApiResponse {
647            ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok")
648        }
649
650        let router = axum::Router::new().route("/", axum::routing::get(handler));
651        let req = Request::builder()
652            .method(Method::GET)
653            .uri("/")
654            .body(Body::empty())
655            .unwrap();
656        let resp = router.oneshot(req).await.unwrap();
657
658        assert_eq!(resp.status(), StatusCode::OK);
659        assert_eq!(
660            resp.headers().get("content-type").unwrap(),
661            "application/json; charset=utf-8"
662        );
663
664        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
665        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
666        let json: Value = serde_json::from_str(&body_str).unwrap();
667
668        assert_eq!(json["code"], 1);
669        assert_eq!(json["msg"], "ok");
670        assert_eq!(json["data"]["id"], 1);
671        assert_eq!(json["data"]["name"], "alice");
672    }
673
674    #[tokio::test]
675    async fn test_render_error_handler_return() {
676        async fn handler() -> Response {
677            render_error("参数错误")
678        }
679
680        let router = axum::Router::new().route("/", axum::routing::post(handler));
681        let req = Request::builder()
682            .method(Method::POST)
683            .uri("/")
684            .body(Body::empty())
685            .unwrap();
686        let resp = router.oneshot(req).await.unwrap();
687
688        assert_eq!(resp.status(), StatusCode::OK);
689
690        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
691        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
692        let json: Value = serde_json::from_str(&body_str).unwrap();
693
694        assert_eq!(json["code"], 0);
695        assert_eq!(json["msg"], "参数错误");
696        assert!(json["data"].is_object());
697    }
698
699    #[tokio::test]
700    async fn test_response_body_exact_format() {
701        // 严格验证响应体格式:{code,msg,data},与 PHP renderJson 完全一致
702        async fn handler() -> ApiResponse {
703            ApiResponse::success_empty()
704        }
705
706        let router = axum::Router::new().route("/", axum::routing::get(handler));
707        let req = Request::builder()
708            .method(Method::GET)
709            .uri("/")
710            .body(Body::empty())
711            .unwrap();
712        let resp = router.oneshot(req).await.unwrap();
713
714        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
715        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
716        assert_eq!(body_str, r#"{"code":1,"msg":"","data":{}}"#);
717    }
718
719    #[tokio::test]
720    async fn test_response_with_complex_data() {
721        async fn handler() -> ApiResponse {
722            ApiResponse::success(
723                serde_json::json!({
724                    "list": [{"id": 1}, {"id": 2}],
725                    "total": 2,
726                    "page": 1,
727                    "size": 10
728                }),
729                "查询成功",
730            )
731        }
732
733        let router = axum::Router::new().route("/", axum::routing::get(handler));
734        let req = Request::builder()
735            .method(Method::GET)
736            .uri("/")
737            .body(Body::empty())
738            .unwrap();
739        let resp = router.oneshot(req).await.unwrap();
740
741        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
742        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
743        let json: Value = serde_json::from_str(&body_str).unwrap();
744
745        assert_eq!(json["code"], 1);
746        assert_eq!(json["msg"], "查询成功");
747        assert_eq!(json["data"]["total"], 2);
748        assert_eq!(json["data"]["list"][0]["id"], 1);
749        assert_eq!(json["data"]["list"][1]["id"], 2);
750    }
751
752    #[tokio::test]
753    async fn test_response_with_various_error_codes() {
754        // 验证各种错误码(对齐 PHP BaseException)
755        let test_cases = vec![
756            (0, "业务失败"),
757            (-1, "未登录"),
758            (-2, "用户不存在"),
759            (-3, "用户被禁用"),
760            (403, "禁止访问"),
761            (404, "资源不存在"),
762            (422, "参数校验失败"),
763            (500, "数据库错误"),
764        ];
765
766        for (code, msg) in test_cases {
767            let resp = ApiResponse::error_with_code(code, msg, Value::Object(Map::new()));
768            let json_str = resp.to_json_string();
769            let json: Value = serde_json::from_str(&json_str).unwrap();
770            assert_eq!(json["code"], code);
771            assert_eq!(json["msg"], msg);
772        }
773    }
774
775    // ====================================================================
776    // PHP 一致性测试(R5 硬约束:PHP/Rust 行为对比)
777    //
778    // 对比 PHP `SzController::renderJson` / `renderSuccess` / `renderError`
779    // 与 Rust `ApiResponse` / `render_json` / `render_success` / `render_error`
780    // 的行为差异。
781    //
782    // PHP 源码(e:\vue\test\鲜视达\server\app\SzController.php):
783    //   protected function renderJson($code = 1, $msg = '', $data = [])
784    //   {
785    //       return compact('code', 'msg', 'data');
786    //   }
787    //
788    //   protected function renderSuccess($msg = 'success', $data = [])
789    //   {
790    //       return json($this->renderJson(1, $msg, $data));
791    //   }
792    //
793    //   protected function renderError($msg = 'error', $data = [], $code = 0)
794    //   {
795    //       return json($this->renderJson($code, $msg, $data));
796    //   }
797    // ====================================================================
798
799    #[test]
800    fn test_php_consistency_render_json_compact_field_order() {
801        // PHP `renderJson` 通过 `compact('code', 'msg', 'data')` 返回数组,
802        // `compact()` 严格按参数顺序保序序列化:code → msg → data。
803        // Rust 使用 `serde_json::Map`(preserve_order)保证相同顺序。
804        let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1}));
805        let value = resp.to_value();
806        let obj = value.as_object().unwrap();
807        let keys: Vec<&String> = obj.keys().collect();
808        assert_eq!(
809            keys,
810            vec!["code", "msg", "data"],
811            "字段顺序必须为 code → msg → data(对齐 PHP compact())"
812        );
813        assert_eq!(value["code"], 1);
814        assert_eq!(value["msg"], "ok");
815        assert_eq!(value["data"]["id"], 1);
816    }
817
818    #[test]
819    fn test_php_consistency_render_json_default_values() {
820        // PHP `renderJson()` 默认值:$code=1, $msg='', $data=[]
821        // 对齐 PHP:`return compact('code', 'msg', 'data');`
822        let resp = ApiResponse::new(1, "", Value::Object(Map::new()));
823        let json_str = resp.to_json_string();
824        assert_eq!(
825            json_str, r#"{"code":1,"msg":"","data":{}}"#,
826            "默认值必须与 PHP renderJson() 一致:code=1, msg='', data={{}}"
827        );
828    }
829
830    #[test]
831    fn test_php_consistency_render_success_calls_render_json_with_code_1() {
832        // PHP `renderSuccess($msg, $data)` 内部调用 `renderJson(1, $msg, $data)`,
833        // 即 code 必须固定为 1。
834        let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
835        assert_eq!(
836            resp.code, 1,
837            "renderSuccess 必须 code=1(对齐 PHP renderJson(1, ...))"
838        );
839        assert_eq!(resp.msg, "ok");
840        assert_eq!(resp.data["id"], 1);
841
842        // 验证完整 JSON 输出格式
843        let json_str = resp.to_json_string();
844        let expected = r#"{"code":1,"msg":"ok","data":{"id":1}}"#;
845        assert_eq!(json_str, expected);
846    }
847
848    #[test]
849    fn test_php_consistency_render_error_default_code_is_0() {
850        // PHP `renderError($msg = 'error', $data = [], $code = 0)` 默认 $code=0
851        // 内部调用 `renderJson($code, $msg, $data)`,即默认 code=0
852        let resp = ApiResponse::error("参数错误");
853        assert_eq!(
854            resp.code, 0,
855            "renderError 默认 code=0(对齐 PHP 默认参数 $code = 0)"
856        );
857        assert_eq!(resp.msg, "参数错误");
858        assert!(
859            resp.data.is_object(),
860            "renderError 默认 data 为空对象(对齐 PHP $data = [])"
861        );
862
863        // 验证 HTTP 状态码始终为 200(对齐 PHP json() 响应)
864        let response = render_error("参数错误");
865        assert_eq!(response.status(), StatusCode::OK);
866    }
867
868    #[test]
869    fn test_php_consistency_render_error_with_custom_code_aligns_base_exception() {
870        // PHP `renderError($msg, $data, $code)` 支持自定义错误码
871        // PHP BaseException 错误码约定:
872        //   -1 = 未登录
873        //   -2 = 用户不存在
874        //   -3 = 用户被禁用
875        // Rust 必须能复刻这些错误码
876        let test_cases = vec![
877            (-1i32, "未登录"),
878            (-2, "用户不存在"),
879            (-3, "用户被禁用"),
880            (0, "业务失败"),
881        ];
882
883        for (code, msg) in test_cases {
884            let resp = ApiResponse::error_with_code(code, msg, Value::Object(Map::new()));
885            let json_str = resp.to_json_string();
886            let json: Value = serde_json::from_str(&json_str).unwrap();
887            assert_eq!(
888                json["code"], code,
889                "自定义错误码必须与 PHP BaseException 约定一致"
890            );
891            assert_eq!(json["msg"], msg);
892            // data 字段必须存在(对齐 PHP compact('code', 'msg', 'data'))
893            assert!(json.get("data").is_some(), "data 字段必须存在");
894        }
895    }
896
897    // ====================================================================
898    // 前后端分离 JSON 默认返回测试
899    //
900    // 测试维度:
901    // 1. DefaultResponseType 枚举(3 种策略)
902    // 2. is_json_request(Accept 头判断)
903    // 3. respond(默认 JSON 响应)
904    // 4. respond_html(HTML 响应)
905    // 5. respond_text(纯文本响应)
906    // 6. auto_respond(PHP autoResponse 对齐 + bug 复刻)
907    // 7. IntoResponse for Value(Value 直接作为 handler 返回值)
908    // 8. R5 PHP/Rust 行为对比(autoResponse + isJson + 数组字面量 "Array" bug)
909    // ====================================================================
910
911    // ---------- DefaultResponseType 枚举测试 ----------
912
913    #[test]
914    fn test_default_response_type_default_is_json() {
915        // 项目主策略:默认为 Json
916        let t = DefaultResponseType::default();
917        assert_eq!(t, DefaultResponseType::Json);
918    }
919
920    #[test]
921    fn test_default_response_type_variants_eq() {
922        assert_eq!(DefaultResponseType::Json, DefaultResponseType::Json);
923        assert_ne!(DefaultResponseType::Json, DefaultResponseType::Html);
924        assert_ne!(DefaultResponseType::Json, DefaultResponseType::Auto);
925        assert_ne!(DefaultResponseType::Html, DefaultResponseType::Auto);
926    }
927
928    #[test]
929    fn test_default_response_type_clone_copy() {
930        let t = DefaultResponseType::Json;
931        let t2 = t; // Copy
932        assert_eq!(t, t2);
933        // DefaultResponseType 实现 Copy,无需 clone
934        let t3 = t;
935        assert_eq!(t, t3);
936    }
937
938    #[test]
939    fn test_default_response_type_debug() {
940        let debug = format!("{:?}", DefaultResponseType::Json);
941        assert!(debug.contains("Json"));
942        let debug = format!("{:?}", DefaultResponseType::Html);
943        assert!(debug.contains("Html"));
944        let debug = format!("{:?}", DefaultResponseType::Auto);
945        assert!(debug.contains("Auto"));
946    }
947
948    #[test]
949    fn test_default_response_type_respond_json() {
950        // Json 策略:始终返回 JSON
951        let headers = HeaderMap::new();
952        let data = serde_json::json!({"id": 1});
953        let resp = DefaultResponseType::Json.respond(&data, &headers);
954        assert_eq!(resp.status(), StatusCode::OK);
955        assert_eq!(
956            resp.headers().get("content-type").unwrap(),
957            "application/json; charset=utf-8"
958        );
959    }
960
961    #[test]
962    fn test_default_response_type_respond_html() {
963        // Html 策略:始终返回 HTML
964        let headers = HeaderMap::new();
965        let data = serde_json::json!({"id": 1});
966        let resp = DefaultResponseType::Html.respond(&data, &headers);
967        assert_eq!(resp.status(), StatusCode::OK);
968        assert_eq!(
969            resp.headers().get("content-type").unwrap(),
970            "text/html; charset=utf-8"
971        );
972    }
973
974    #[tokio::test]
975    async fn test_default_response_type_respond_html_body() {
976        let headers = HeaderMap::new();
977        let data = serde_json::json!({"id": 1});
978        let resp = DefaultResponseType::Html.respond(&data, &headers);
979        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
980        let body = String::from_utf8(bytes.to_vec()).unwrap();
981        // HTML 策略将 Value 序列化为字符串
982        assert_eq!(body, r#"{"id":1}"#);
983    }
984
985    #[tokio::test]
986    async fn test_default_response_type_respond_auto_with_json_accept() {
987        // Auto 策略 + Accept: application/json → JSON 响应
988        let mut headers = HeaderMap::new();
989        headers.insert("accept", "application/json".parse().unwrap());
990        let data = serde_json::json!({"id": 1});
991        let resp = DefaultResponseType::Auto.respond(&data, &headers);
992        assert_eq!(
993            resp.headers().get("content-type").unwrap(),
994            "application/json; charset=utf-8"
995        );
996    }
997
998    #[tokio::test]
999    async fn test_default_response_type_respond_auto_with_html_accept() {
1000        // Auto 策略 + Accept: text/html → HTML 响应(数组字面量 "Array" bug 复刻)
1001        let mut headers = HeaderMap::new();
1002        headers.insert("accept", "text/html".parse().unwrap());
1003        let data = serde_json::json!({"id": 1});
1004        let resp = DefaultResponseType::Auto.respond(&data, &headers);
1005        assert_eq!(
1006            resp.headers().get("content-type").unwrap(),
1007            "text/html; charset=utf-8"
1008        );
1009        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1010        let body = String::from_utf8(bytes.to_vec()).unwrap();
1011        // PHP bug 复刻:对象输出字面量 "Array"
1012        assert_eq!(body, "Array");
1013    }
1014
1015    // ---------- is_json_request 测试 ----------
1016
1017    #[test]
1018    fn test_is_json_request_with_application_json() {
1019        let mut headers = HeaderMap::new();
1020        headers.insert("accept", "application/json".parse().unwrap());
1021        assert!(is_json_request(&headers));
1022    }
1023
1024    #[test]
1025    fn test_is_json_request_with_text_json() {
1026        let mut headers = HeaderMap::new();
1027        headers.insert("accept", "text/json".parse().unwrap());
1028        assert!(is_json_request(&headers));
1029    }
1030
1031    #[test]
1032    fn test_is_json_request_with_vnd_api_json() {
1033        let mut headers = HeaderMap::new();
1034        headers.insert("accept", "application/vnd.api+json".parse().unwrap());
1035        assert!(is_json_request(&headers));
1036    }
1037
1038    #[test]
1039    fn test_is_json_request_with_wildcard() {
1040        // Accept: */* 不包含 "json" 子串,应返回 false
1041        let mut headers = HeaderMap::new();
1042        headers.insert("accept", "*/*".parse().unwrap());
1043        assert!(!is_json_request(&headers));
1044    }
1045
1046    #[test]
1047    fn test_is_json_request_with_text_html() {
1048        let mut headers = HeaderMap::new();
1049        headers.insert("accept", "text/html".parse().unwrap());
1050        assert!(!is_json_request(&headers));
1051    }
1052
1053    #[test]
1054    fn test_is_json_request_no_accept_header() {
1055        let headers = HeaderMap::new();
1056        assert!(!is_json_request(&headers));
1057    }
1058
1059    #[test]
1060    fn test_is_json_request_case_insensitive() {
1061        // 大小写不敏感(对齐 PHP strpos 在大小写不敏感场景的行为)
1062        let mut headers = HeaderMap::new();
1063        headers.insert("accept", "APPLICATION/JSON".parse().unwrap());
1064        assert!(is_json_request(&headers));
1065    }
1066
1067    #[test]
1068    fn test_is_json_request_mixed_accept() {
1069        // 浏览器可能发送复杂 Accept 头
1070        let mut headers = HeaderMap::new();
1071        headers.insert(
1072            "accept",
1073            "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
1074                .parse()
1075                .unwrap(),
1076        );
1077        assert!(is_json_request(&headers));
1078    }
1079
1080    // ---------- respond 测试 ----------
1081
1082    #[test]
1083    fn test_respond_returns_json_content_type() {
1084        let data = serde_json::json!({"id": 1});
1085        let resp = respond(&data);
1086        assert_eq!(resp.status(), StatusCode::OK);
1087        assert_eq!(
1088            resp.headers().get("content-type").unwrap(),
1089            "application/json; charset=utf-8"
1090        );
1091    }
1092
1093    #[tokio::test]
1094    async fn test_respond_object_body() {
1095        let data = serde_json::json!({"id": 1, "name": "alice"});
1096        let resp = respond(&data);
1097        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1098        let body = String::from_utf8(bytes.to_vec()).unwrap();
1099        assert_eq!(body, r#"{"id":1,"name":"alice"}"#);
1100    }
1101
1102    #[tokio::test]
1103    async fn test_respond_array_body() {
1104        let data = serde_json::json!([1, 2, 3]);
1105        let resp = respond(&data);
1106        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1107        let body = String::from_utf8(bytes.to_vec()).unwrap();
1108        assert_eq!(body, r#"[1,2,3]"#);
1109    }
1110
1111    #[tokio::test]
1112    async fn test_respond_string_value() {
1113        let data = Value::String("hello".to_string());
1114        let resp = respond(&data);
1115        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1116        let body = String::from_utf8(bytes.to_vec()).unwrap();
1117        // Value::String 序列化为 JSON 字符串(带引号)
1118        assert_eq!(body, r#""hello""#);
1119    }
1120
1121    #[tokio::test]
1122    async fn test_respond_null_value() {
1123        let resp = respond(&Value::Null);
1124        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1125        let body = String::from_utf8(bytes.to_vec()).unwrap();
1126        assert_eq!(body, "null");
1127    }
1128
1129    #[tokio::test]
1130    async fn test_respond_number_value() {
1131        let resp = respond(&serde_json::json!(42));
1132        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1133        let body = String::from_utf8(bytes.to_vec()).unwrap();
1134        assert_eq!(body, "42");
1135    }
1136
1137    #[tokio::test]
1138    async fn test_respond_bool_value() {
1139        let resp = respond(&serde_json::json!(true));
1140        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1141        let body = String::from_utf8(bytes.to_vec()).unwrap();
1142        assert_eq!(body, "true");
1143    }
1144
1145    // ---------- respond_html 测试 ----------
1146
1147    #[test]
1148    fn test_respond_html_content_type() {
1149        let resp = respond_html("<h1>Hello</h1>");
1150        assert_eq!(resp.status(), StatusCode::OK);
1151        assert_eq!(
1152            resp.headers().get("content-type").unwrap(),
1153            "text/html; charset=utf-8"
1154        );
1155    }
1156
1157    #[tokio::test]
1158    async fn test_respond_html_body() {
1159        let resp = respond_html("<p>test</p>");
1160        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1161        let body = String::from_utf8(bytes.to_vec()).unwrap();
1162        assert_eq!(body, "<p>test</p>");
1163    }
1164
1165    #[tokio::test]
1166    async fn test_respond_html_empty() {
1167        let resp = respond_html("");
1168        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1169        let body = String::from_utf8(bytes.to_vec()).unwrap();
1170        assert_eq!(body, "");
1171    }
1172
1173    #[tokio::test]
1174    async fn test_respond_html_with_unicode() {
1175        let resp = respond_html("<p>你好世界</p>");
1176        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1177        let body = String::from_utf8(bytes.to_vec()).unwrap();
1178        assert_eq!(body, "<p>你好世界</p>");
1179    }
1180
1181    // ---------- respond_text 测试 ----------
1182
1183    #[test]
1184    fn test_respond_text_content_type() {
1185        let resp = respond_text("plain text");
1186        assert_eq!(resp.status(), StatusCode::OK);
1187        assert_eq!(
1188            resp.headers().get("content-type").unwrap(),
1189            "text/plain; charset=utf-8"
1190        );
1191    }
1192
1193    #[tokio::test]
1194    async fn test_respond_text_body() {
1195        let resp = respond_text("OK");
1196        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1197        let body = String::from_utf8(bytes.to_vec()).unwrap();
1198        assert_eq!(body, "OK");
1199    }
1200
1201    // ---------- auto_respond 测试 ----------
1202
1203    #[tokio::test]
1204    async fn test_auto_respond_json_request_with_object() {
1205        // Accept: application/json + 对象 → JSON 响应
1206        let mut headers = HeaderMap::new();
1207        headers.insert("accept", "application/json".parse().unwrap());
1208        let data = serde_json::json!({"id": 1});
1209        let resp = auto_respond(&data, &headers);
1210        assert_eq!(
1211            resp.headers().get("content-type").unwrap(),
1212            "application/json; charset=utf-8"
1213        );
1214        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1215        let body = String::from_utf8(bytes.to_vec()).unwrap();
1216        assert_eq!(body, r#"{"id":1}"#);
1217    }
1218
1219    #[tokio::test]
1220    async fn test_auto_respond_json_request_with_array() {
1221        let mut headers = HeaderMap::new();
1222        headers.insert("accept", "application/json".parse().unwrap());
1223        let data = serde_json::json!([1, 2, 3]);
1224        let resp = auto_respond(&data, &headers);
1225        assert_eq!(
1226            resp.headers().get("content-type").unwrap(),
1227            "application/json; charset=utf-8"
1228        );
1229        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1230        let body = String::from_utf8(bytes.to_vec()).unwrap();
1231        assert_eq!(body, r#"[1,2,3]"#);
1232    }
1233
1234    #[tokio::test]
1235    async fn test_auto_respond_html_request_with_object_returns_array_literal() {
1236        // PHP bug 复刻:Accept: text/html + 对象 → 字面量 "Array"
1237        let mut headers = HeaderMap::new();
1238        headers.insert("accept", "text/html".parse().unwrap());
1239        let data = serde_json::json!({"id": 1});
1240        let resp = auto_respond(&data, &headers);
1241        assert_eq!(
1242            resp.headers().get("content-type").unwrap(),
1243            "text/html; charset=utf-8"
1244        );
1245        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1246        let body = String::from_utf8(bytes.to_vec()).unwrap();
1247        assert_eq!(body, "Array");
1248    }
1249
1250    #[tokio::test]
1251    async fn test_auto_respond_html_request_with_array_returns_array_literal() {
1252        // PHP bug 复刻:Accept: text/html + 数组 → 字面量 "Array"
1253        let mut headers = HeaderMap::new();
1254        headers.insert("accept", "text/html".parse().unwrap());
1255        let data = serde_json::json!([1, 2, 3]);
1256        let resp = auto_respond(&data, &headers);
1257        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1258        let body = String::from_utf8(bytes.to_vec()).unwrap();
1259        assert_eq!(body, "Array");
1260    }
1261
1262    #[tokio::test]
1263    async fn test_auto_respond_html_request_with_string_returns_string() {
1264        // Accept: text/html + 字符串 → 字符串内容(非字面量 "Array")
1265        let mut headers = HeaderMap::new();
1266        headers.insert("accept", "text/html".parse().unwrap());
1267        let data = Value::String("hello".to_string());
1268        let resp = auto_respond(&data, &headers);
1269        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1270        let body = String::from_utf8(bytes.to_vec()).unwrap();
1271        assert_eq!(body, "hello");
1272    }
1273
1274    #[tokio::test]
1275    async fn test_auto_respond_html_request_with_null_returns_empty() {
1276        let mut headers = HeaderMap::new();
1277        headers.insert("accept", "text/html".parse().unwrap());
1278        let resp = auto_respond(&Value::Null, &headers);
1279        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1280        let body = String::from_utf8(bytes.to_vec()).unwrap();
1281        assert_eq!(body, "");
1282    }
1283
1284    #[tokio::test]
1285    async fn test_auto_respond_html_request_with_number_returns_number_string() {
1286        let mut headers = HeaderMap::new();
1287        headers.insert("accept", "text/html".parse().unwrap());
1288        let resp = auto_respond(&serde_json::json!(42), &headers);
1289        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1290        let body = String::from_utf8(bytes.to_vec()).unwrap();
1291        assert_eq!(body, "42");
1292    }
1293
1294    #[tokio::test]
1295    async fn test_auto_respond_no_accept_header_returns_html() {
1296        // 无 Accept 头 → 视为非 JSON 请求 → HTML 响应
1297        let headers = HeaderMap::new();
1298        let data = serde_json::json!({"id": 1});
1299        let resp = auto_respond(&data, &headers);
1300        assert_eq!(
1301            resp.headers().get("content-type").unwrap(),
1302            "text/html; charset=utf-8"
1303        );
1304    }
1305
1306    #[tokio::test]
1307    async fn test_auto_respond_wildcard_accept_returns_html() {
1308        // Accept: */* 不包含 "json" → HTML 响应(对齐 PHP isJson() 返回 false)
1309        let mut headers = HeaderMap::new();
1310        headers.insert("accept", "*/*".parse().unwrap());
1311        let data = serde_json::json!({"id": 1});
1312        let resp = auto_respond(&data, &headers);
1313        assert_eq!(
1314            resp.headers().get("content-type").unwrap(),
1315            "text/html; charset=utf-8"
1316        );
1317    }
1318
1319    // ---------- IntoResponse for JsonResponse 测试 ----------
1320
1321    #[tokio::test]
1322    async fn test_json_response_into_response_object() {
1323        async fn handler() -> JsonResponse {
1324            JsonResponse(serde_json::json!({"id": 1, "name": "alice"}))
1325        }
1326
1327        let router = axum::Router::new().route("/", axum::routing::get(handler));
1328        let req = Request::builder()
1329            .method(Method::GET)
1330            .uri("/")
1331            .body(Body::empty())
1332            .unwrap();
1333        let resp = router.oneshot(req).await.unwrap();
1334
1335        assert_eq!(resp.status(), StatusCode::OK);
1336        assert_eq!(
1337            resp.headers().get("content-type").unwrap(),
1338            "application/json; charset=utf-8"
1339        );
1340        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1341        let body = String::from_utf8(bytes.to_vec()).unwrap();
1342        assert_eq!(body, r#"{"id":1,"name":"alice"}"#);
1343    }
1344
1345    #[tokio::test]
1346    async fn test_json_response_into_response_array() {
1347        async fn handler() -> JsonResponse {
1348            JsonResponse(serde_json::json!([1, 2, 3]))
1349        }
1350
1351        let router = axum::Router::new().route("/", axum::routing::get(handler));
1352        let req = Request::builder()
1353            .method(Method::GET)
1354            .uri("/")
1355            .body(Body::empty())
1356            .unwrap();
1357        let resp = router.oneshot(req).await.unwrap();
1358
1359        assert_eq!(
1360            resp.headers().get("content-type").unwrap(),
1361            "application/json; charset=utf-8"
1362        );
1363        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1364        let body = String::from_utf8(bytes.to_vec()).unwrap();
1365        assert_eq!(body, r#"[1,2,3]"#);
1366    }
1367
1368    #[tokio::test]
1369    async fn test_json_response_into_response_string() {
1370        async fn handler() -> JsonResponse {
1371            JsonResponse(Value::String("hello".to_string()))
1372        }
1373
1374        let router = axum::Router::new().route("/", axum::routing::get(handler));
1375        let req = Request::builder()
1376            .method(Method::GET)
1377            .uri("/")
1378            .body(Body::empty())
1379            .unwrap();
1380        let resp = router.oneshot(req).await.unwrap();
1381
1382        // Value::String 通过 JsonResponse 返回 JSON 响应(带引号)
1383        assert_eq!(
1384            resp.headers().get("content-type").unwrap(),
1385            "application/json; charset=utf-8"
1386        );
1387        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1388        let body = String::from_utf8(bytes.to_vec()).unwrap();
1389        assert_eq!(body, r#""hello""#);
1390    }
1391
1392    #[tokio::test]
1393    async fn test_json_response_into_response_null() {
1394        async fn handler() -> JsonResponse {
1395            JsonResponse(Value::Null)
1396        }
1397
1398        let router = axum::Router::new().route("/", axum::routing::get(handler));
1399        let req = Request::builder()
1400            .method(Method::GET)
1401            .uri("/")
1402            .body(Body::empty())
1403            .unwrap();
1404        let resp = router.oneshot(req).await.unwrap();
1405
1406        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1407        let body = String::from_utf8(bytes.to_vec()).unwrap();
1408        assert_eq!(body, "null");
1409    }
1410
1411    #[tokio::test]
1412    async fn test_json_response_into_response_post_handler() {
1413        // 模拟前后端分离典型场景:POST 请求 → 处理 → 返回 JsonResponse
1414        async fn handler() -> JsonResponse {
1415            JsonResponse(serde_json::json!({
1416                "code": 1,
1417                "msg": "success",
1418                "data": {"id": 12345, "status": "paid"}
1419            }))
1420        }
1421
1422        let router = axum::Router::new().route("/api/order", axum::routing::post(handler));
1423        let req = Request::builder()
1424            .method(Method::POST)
1425            .uri("/api/order")
1426            .body(Body::empty())
1427            .unwrap();
1428        let resp = router.oneshot(req).await.unwrap();
1429
1430        assert_eq!(resp.status(), StatusCode::OK);
1431        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1432        let body = String::from_utf8(bytes.to_vec()).unwrap();
1433        let json: Value = serde_json::from_str(&body).unwrap();
1434        assert_eq!(json["code"], 1);
1435        assert_eq!(json["msg"], "success");
1436        assert_eq!(json["data"]["id"], 12345);
1437        assert_eq!(json["data"]["status"], "paid");
1438    }
1439
1440    #[test]
1441    fn test_json_response_from_value() {
1442        // From<Value> 转换测试
1443        let value = serde_json::json!({"id": 1});
1444        let json_resp: JsonResponse = value.clone().into();
1445        assert_eq!(json_resp.0, value);
1446    }
1447
1448    #[test]
1449    fn test_json_response_clone_debug() {
1450        let resp = JsonResponse(serde_json::json!({"id": 1}));
1451        let cloned = resp.clone();
1452        assert_eq!(resp.0, cloned.0);
1453
1454        let debug = format!("{resp:?}");
1455        assert!(debug.contains("JsonResponse"));
1456    }
1457
1458    // ---------- R5 PHP/Rust 行为对比测试 ----------
1459    //
1460    // 对比 PHP ThinkPHP 6 `Dispatch::autoResponse()` + `Request::isJson()` 的行为
1461    //
1462    // PHP 源码:
1463    //   vendor/topthink/framework/src/think/route/Dispatch.php:84-107 autoResponse()
1464    //   vendor/topthink/framework/src/think/Request.php:1557-1562 isJson()
1465    //
1466    // PHP autoResponse 行为:
1467    // 1. $data instanceof Response → 直接返回(Rust: Response → 直接返回)
1468    // 2. $data instanceof ResponseInterface → 转换为 Response
1469    // 3. $data !== null → isJson() ? 'json' : 'html'
1470    //    - isJson=true → JSON 响应(数组 → JSON 编码)
1471    //    - isJson=false → HTML 响应(数组 → 字面量 "Array")
1472    // 4. $data === null → ob_get_clean + html
1473    //
1474    // PHP isJson 行为:
1475    // - 检查 Accept 头是否包含 "json" 子串
1476    // - 大小写敏感(PHP strpos 是大小写敏感的)
1477    // - 注意:PHP type() 方法使用 stristr(大小写不敏感),所以 PHP isJson() 实际是大小写不敏感的
1478    // ----------------------------------------------------------------
1479
1480    #[test]
1481    fn test_r5_php_isjson_accept_application_json() {
1482        // PHP: Accept: application/json → isJson() 返回 true
1483        let mut headers = HeaderMap::new();
1484        headers.insert("accept", "application/json".parse().unwrap());
1485        assert!(
1486            is_json_request(&headers),
1487            "Accept: application/json 时 isJson() 必须返回 true(对齐 PHP)"
1488        );
1489    }
1490
1491    #[test]
1492    fn test_r5_php_isjson_accept_text_html() {
1493        // PHP: Accept: text/html → isJson() 返回 false
1494        let mut headers = HeaderMap::new();
1495        headers.insert("accept", "text/html".parse().unwrap());
1496        assert!(
1497            !is_json_request(&headers),
1498            "Accept: text/html 时 isJson() 必须返回 false(对齐 PHP)"
1499        );
1500    }
1501
1502    #[test]
1503    fn test_r5_php_isjson_accept_wildcard() {
1504        // PHP: Accept: */* → type() 返回 ''(无匹配 MIME)→ isJson() 返回 false
1505        let mut headers = HeaderMap::new();
1506        headers.insert("accept", "*/*".parse().unwrap());
1507        assert!(
1508            !is_json_request(&headers),
1509            "Accept: */* 时 isJson() 必须返回 false(对齐 PHP type() 无匹配 MIME)"
1510        );
1511    }
1512
1513    #[test]
1514    fn test_r5_php_isjson_no_accept_header() {
1515        // PHP: 无 Accept 头 → type() 返回 '' → isJson() 返回 false
1516        let headers = HeaderMap::new();
1517        assert!(
1518            !is_json_request(&headers),
1519            "无 Accept 头时 isJson() 必须返回 false(对齐 PHP)"
1520        );
1521    }
1522
1523    #[tokio::test]
1524    async fn test_r5_php_autoresponse_json_type_with_array() {
1525        // PHP: autoResponse($array) + isJson=true → Response::create($array, 'json')
1526        // → JSON 响应(数组被 json_encode)
1527        let mut headers = HeaderMap::new();
1528        headers.insert("accept", "application/json".parse().unwrap());
1529        let data = serde_json::json!([1, 2, 3]);
1530        let resp = auto_respond(&data, &headers);
1531
1532        // 验证 Content-Type 为 JSON
1533        assert_eq!(
1534            resp.headers().get("content-type").unwrap(),
1535            "application/json; charset=utf-8",
1536            "PHP autoResponse + isJson=true 时必须返回 JSON 类型"
1537        );
1538
1539        // 验证响应体为 JSON 编码的数组
1540        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1541        let body = String::from_utf8(bytes.to_vec()).unwrap();
1542        assert_eq!(
1543            body, "[1,2,3]",
1544            "PHP autoResponse + isJson=true 时数组必须被 json_encode"
1545        );
1546    }
1547
1548    #[tokio::test]
1549    async fn test_r5_php_autoresponse_html_type_with_array_returns_array_literal() {
1550        // PHP bug 复刻:autoResponse($array) + isJson=false → Response::create($array, 'html')
1551        // → PHP `print($array)` 输出字面量 "Array"
1552        // Rust 严格对齐此 bug
1553        let mut headers = HeaderMap::new();
1554        headers.insert("accept", "text/html".parse().unwrap());
1555        let data = serde_json::json!([1, 2, 3]);
1556        let resp = auto_respond(&data, &headers);
1557
1558        // 验证 Content-Type 为 HTML
1559        assert_eq!(
1560            resp.headers().get("content-type").unwrap(),
1561            "text/html; charset=utf-8",
1562            "PHP autoResponse + isJson=false 时必须返回 HTML 类型"
1563        );
1564
1565        // 验证响应体为字面量 "Array"(PHP bug)
1566        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1567        let body = String::from_utf8(bytes.to_vec()).unwrap();
1568        assert_eq!(
1569            body, "Array",
1570            "PHP autoResponse + isJson=false 时数组必须输出字面量 'Array'(PHP bug 复刻)"
1571        );
1572    }
1573
1574    #[tokio::test]
1575    async fn test_r5_php_autoresponse_html_type_with_object_returns_array_literal() {
1576        // PHP bug 复刻:autoResponse($assocArray) + isJson=false
1577        // PHP 中关联数组也是数组,print 输出 "Array"
1578        let mut headers = HeaderMap::new();
1579        headers.insert("accept", "text/html".parse().unwrap());
1580        let data = serde_json::json!({"name": "alice", "age": 30});
1581        let resp = auto_respond(&data, &headers);
1582
1583        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1584        let body = String::from_utf8(bytes.to_vec()).unwrap();
1585        assert_eq!(
1586            body, "Array",
1587            "PHP autoResponse + isJson=false 时关联数组也输出字面量 'Array'(PHP bug 复刻)"
1588        );
1589    }
1590
1591    #[tokio::test]
1592    async fn test_r5_php_autoresponse_html_type_with_string_returns_string() {
1593        // PHP: autoResponse($string) + isJson=false → Response::create($string, 'html')
1594        // → 字符串原样输出
1595        let mut headers = HeaderMap::new();
1596        headers.insert("accept", "text/html".parse().unwrap());
1597        let data = Value::String("Hello World".to_string());
1598        let resp = auto_respond(&data, &headers);
1599
1600        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1601        let body = String::from_utf8(bytes.to_vec()).unwrap();
1602        assert_eq!(
1603            body, "Hello World",
1604            "PHP autoResponse + isJson=false + 字符串时必须原样输出字符串内容"
1605        );
1606    }
1607
1608    #[tokio::test]
1609    async fn test_r5_php_autoresponse_no_accept_header_returns_html() {
1610        // PHP: 无 Accept 头 → isJson=false → HTML 响应
1611        let headers = HeaderMap::new();
1612        let data = serde_json::json!({"id": 1});
1613        let resp = auto_respond(&data, &headers);
1614
1615        assert_eq!(
1616            resp.headers().get("content-type").unwrap(),
1617            "text/html; charset=utf-8",
1618            "无 Accept 头时 PHP isJson() 返回 false,必须返回 HTML 类型"
1619        );
1620    }
1621
1622    #[tokio::test]
1623    async fn test_r5_php_autoresponse_wildcard_accept_returns_html() {
1624        // PHP: Accept: */* → type() 返回 ''(无匹配)→ isJson=false → HTML 响应
1625        let mut headers = HeaderMap::new();
1626        headers.insert("accept", "*/*".parse().unwrap());
1627        let data = serde_json::json!({"id": 1});
1628        let resp = auto_respond(&data, &headers);
1629
1630        assert_eq!(
1631            resp.headers().get("content-type").unwrap(),
1632            "text/html; charset=utf-8",
1633            "Accept: */* 时 PHP isJson() 返回 false,必须返回 HTML 类型"
1634        );
1635    }
1636
1637    #[tokio::test]
1638    async fn test_r5_php_autoresponse_mixed_accept_with_json() {
1639        // PHP: Accept: text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8
1640        // → type() 匹配到 json → isJson=true → JSON 响应
1641        let mut headers = HeaderMap::new();
1642        headers.insert(
1643            "accept",
1644            "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
1645                .parse()
1646                .unwrap(),
1647        );
1648        let data = serde_json::json!({"id": 1});
1649        let resp = auto_respond(&data, &headers);
1650
1651        assert_eq!(
1652            resp.headers().get("content-type").unwrap(),
1653            "application/json; charset=utf-8",
1654            "Accept 头含 json MIME 时 PHP isJson() 返回 true,必须返回 JSON 类型"
1655        );
1656    }
1657
1658    #[test]
1659    fn test_r5_php_isjson_case_insensitive_alignment() {
1660        // PHP type() 方法使用 stristr(大小写不敏感),所以 isJson() 实际是大小写不敏感的
1661        // Rust 实现使用 to_lowercase().contains("json") 对齐此行为
1662        let mut headers_upper = HeaderMap::new();
1663        headers_upper.insert("accept", "APPLICATION/JSON".parse().unwrap());
1664        assert!(
1665            is_json_request(&headers_upper),
1666            "PHP isJson() 大小写不敏感(stristr),Rust 必须对齐"
1667        );
1668
1669        let mut headers_mixed = HeaderMap::new();
1670        headers_mixed.insert("accept", "Application/Json".parse().unwrap());
1671        assert!(
1672            is_json_request(&headers_mixed),
1673            "PHP isJson() 大小写不敏感(stristr),Rust 必须对齐"
1674        );
1675    }
1676
1677    #[tokio::test]
1678    async fn test_r5_php_default_response_type_json_is_project_main_strategy() {
1679        // 项目主策略:前后端分离 JSON 默认返回
1680        // 与 PHP 不同(PHP 依赖 Accept 头),Rust 项目主策略默认返回 JSON
1681        // 这使得即使没有 Accept: application/json 头,也返回 JSON
1682        let headers = HeaderMap::new(); // 无 Accept 头
1683        let data = serde_json::json!({"id": 1, "name": "alice"});
1684
1685        // 使用 DefaultResponseType::Json(项目主策略)
1686        let resp = DefaultResponseType::Json.respond(&data, &headers);
1687        assert_eq!(
1688            resp.headers().get("content-type").unwrap(),
1689            "application/json; charset=utf-8",
1690            "项目主策略:默认返回 JSON,不受 Accept 头影响"
1691        );
1692
1693        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1694        let body = String::from_utf8(bytes.to_vec()).unwrap();
1695        assert_eq!(
1696            body, r#"{"id":1,"name":"alice"}"#,
1697            "项目主策略:默认返回 JSON 编码的内容"
1698        );
1699    }
1700
1701    #[tokio::test]
1702    async fn test_r5_php_json_response_default_json_strategy() {
1703        // 项目主策略:JsonResponse 直接作为 handler 返回值 → 默认 JSON 响应
1704        // 这与 PHP 的 autoResponse 不同(PHP 会检查 Accept 头),
1705        // 但与项目实际开发约定一致(始终使用 renderSuccess/renderError 返回 JSON)
1706        async fn handler() -> JsonResponse {
1707            JsonResponse(serde_json::json!({"code": 1, "msg": "ok", "data": {"id": 1}}))
1708        }
1709
1710        let router = axum::Router::new().route("/", axum::routing::get(handler));
1711
1712        // 即使发送 Accept: text/html,JsonResponse 也返回 JSON(项目主策略)
1713        let req = Request::builder()
1714            .method(Method::GET)
1715            .uri("/")
1716            .header("accept", "text/html")
1717            .body(Body::empty())
1718            .unwrap();
1719        let resp = router.oneshot(req).await.unwrap();
1720
1721        assert_eq!(
1722            resp.headers().get("content-type").unwrap(),
1723            "application/json; charset=utf-8",
1724            "项目主策略:JsonResponse IntoResponse 始终返回 JSON,不受 Accept 头影响"
1725        );
1726
1727        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1728        let body = String::from_utf8(bytes.to_vec()).unwrap();
1729        assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
1730    }
1731}