Skip to main content

sz_rust_http_facade/
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_http_facade::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    /// 序列化为 `bytes::Bytes`(零拷贝引用计数字节容器)
122    ///
123    /// P3 优化:替代 `to_json_string` 的 String 分配,
124    /// 使用 `serde_json::to_vec` 序列化到 `Vec<u8>` 后转为 `Bytes`,
125    /// 避免 String 的 UTF-8 验证开销,支持零拷贝传递。
126    ///
127    /// 输出与 `to_json_string` 逐字节一致。
128    pub fn to_json_bytes(&self) -> bytes::Bytes {
129        let vec = serde_json::to_vec(&self.to_value())
130            .expect("ApiResponse::to_json_bytes: serde_json::to_vec infallible for Value");
131        bytes::Bytes::from(vec)
132    }
133}
134
135impl Serialize for ApiResponse {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: serde::Serializer,
139    {
140        self.to_value().serialize(serializer)
141    }
142}
143
144/// 让 ApiResponse 可以直接作为 axum handler 返回值
145///
146/// 自动设置:
147/// - HTTP 状态码:200(无论业务 code 是 1 还是 0,HTTP 都是 200,对齐 PHP 行为)
148/// - Content-Type: `application/json; charset=utf-8`
149impl IntoResponse for ApiResponse {
150    fn into_response(self) -> Response {
151        let body = self.to_json_string();
152        (
153            StatusCode::OK,
154            [(
155                axum::http::header::CONTENT_TYPE,
156                "application/json; charset=utf-8",
157            )],
158            body,
159        )
160            .into_response()
161    }
162}
163
164/// 直接构建标准 JSON 响应(无需创建 ApiResponse 实例)
165///
166/// 对齐 PHP `renderJson($code, $msg, $data)`。
167#[tracing::instrument(skip(msg, data))]
168pub fn render_json(code: i32, msg: impl Into<String>, data: Value) -> Response {
169    ApiResponse::new(code, msg, data).into_response()
170}
171
172/// 构建成功响应
173///
174/// 对齐 PHP `renderSuccess($data, $msg)`。
175#[tracing::instrument(skip(data, msg))]
176pub fn render_success(data: Value, msg: impl Into<String>) -> Response {
177    ApiResponse::success(data, msg).into_response()
178}
179
180/// 构建错误响应
181///
182/// 对齐 PHP `renderError($msg, $data)`。
183#[tracing::instrument(skip(msg))]
184pub fn render_error(msg: impl Into<String>) -> Response {
185    ApiResponse::error(msg).into_response()
186}
187
188/// 构建带自定义错误码的错误响应
189///
190/// 对齐 PHP `renderError($code, $msg, $data)`。
191#[tracing::instrument(skip(msg, data))]
192pub fn render_error_with_code(code: i32, msg: impl Into<String>, data: Value) -> Response {
193    ApiResponse::error_with_code(code, msg, data).into_response()
194}
195
196// ============================================================================
197// 前后端分离 JSON 默认返回(项目主策略)
198//
199// 对齐 PHP ThinkPHP 6 `Dispatch::autoResponse()` 行为,但将 JSON 设为默认响应类型
200// (项目主策略:前后端分离)。PHP `autoResponse()` 根据 `$this->request->isJson()`
201// 判断响应类型:isJson → JSON,否则 → HTML(数组会被输出为字面量 "Array")。
202//
203// 本模块扩展三种策略:
204// 1. `DefaultResponseType::Json` — 项目主策略:默认返回 JSON(不渲染模板)
205// 2. `DefaultResponseType::Html` — 兜底场景:返回 HTML(模板渲染使用)
206// 3. `DefaultResponseType::Auto` — 对齐 PHP autoResponse:根据 Accept 头判断
207//
208// ## PHP 源码参考
209//
210// ```php
211// // vendor/topthink/framework/src/think/route/Dispatch.php:84-107
212// protected function autoResponse($data): Response
213// {
214//     if ($data instanceof Response) {
215//         $response = $data;
216//     } elseif ($data instanceof ResponseInterface) {
217//         $response = Response::create((string) $data->getBody(), 'html', $data->getStatusCode());
218//         foreach ($data->getHeaders() as $header => $values) {
219//             $response->header([$header => implode(", ", $values)]);
220//         }
221//     } elseif (!is_null($data)) {
222//         // 默认自动识别响应输出类型
223//         $type     = $this->request->isJson() ? 'json' : 'html';
224//         $response = Response::create($data, $type);
225//     } else {
226//         $data = ob_get_clean();
227//         $content  = false === $data ? '' : $data;
228//         $status   = '' === $content && $this->request->isJson() ? 204 : 200;
229//         $response = Response::create($content, 'html', $status);
230//     }
231//     return $response;
232// }
233// ```
234//
235// ```php
236// // vendor/topthink/framework/src/think/Request.php:1557-1562
237// public function isJson(): bool
238// {
239//     $acceptType = $this->type();
240//     return false !== strpos($acceptType, 'json');
241// }
242// ```
243// ============================================================================
244
245use axum::http::{header, HeaderMap};
246
247/// 默认响应类型策略(对齐 PHP `autoResponse` + 项目主策略扩展)
248///
249/// 项目主策略为前后端分离,因此默认使用 `Json`。`Auto` 严格对齐 PHP TP 6
250/// `autoResponse()` 的行为(根据 Accept 头判断)。`Html` 用于兜底场景
251/// (如模板渲染、PDF/Excel 导出)。
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
253pub enum DefaultResponseType {
254    /// 项目主策略:默认返回 JSON(Content-Type: application/json; charset=utf-8)
255    ///
256    /// 控制器返回 `Value`(数组/对象)时,自动序列化为 JSON 响应。
257    /// 不渲染模板,不检查 Accept 头。
258    #[default]
259    Json,
260
261    /// 兜底场景:返回 HTML(Content-Type: text/html; charset=utf-8)
262    ///
263    /// 用于模板渲染、PDF/Excel 导出等非 JSON 场景。
264    Html,
265
266    /// 对齐 PHP `autoResponse`:根据请求 `Accept` 头判断
267    ///
268    /// - Accept 含 `json` MIME → JSON 响应
269    /// - Accept 不含 `json` MIME → HTML 响应(数组输出字面量 "Array",对齐 PHP bug)
270    Auto,
271}
272
273impl DefaultResponseType {
274    /// 根据策略和请求数据生成响应
275    ///
276    /// # 参数
277    /// - `data`:响应数据(`Value::Object` / `Value::Array` / `Value::String` 等)
278    /// - `headers`:请求头(用于 `Auto` 策略判断 `Accept` 头)
279    ///
280    /// # 返回
281    /// - `Json` → `respond(data)`
282    /// - `Html` → `respond_html(data.to_string())`
283    /// - `Auto` → `auto_respond(data, headers)`
284    pub fn respond(&self, data: &Value, headers: &HeaderMap) -> Response {
285        match self {
286            DefaultResponseType::Json => respond(data),
287            DefaultResponseType::Html => respond_html(data.to_string()),
288            DefaultResponseType::Auto => auto_respond(data, headers),
289        }
290    }
291}
292
293/// 检查请求是否为 JSON 请求(对齐 PHP `Request::isJson()`)
294///
295/// PHP 逻辑:检查 `Accept` 请求头是否包含 `json` MIME 类型
296/// (如 `application/json`、`text/json`、`application/vnd.api+json`)。
297///
298/// # PHP 对齐
299///
300/// ```php
301/// // vendor/topthink/framework/src/think/Request.php:1557-1562
302/// public function isJson(): bool
303/// {
304///     $acceptType = $this->type();
305///     return false !== strpos($acceptType, 'json');
306/// }
307/// ```
308///
309/// # 参数
310///
311/// - `headers`:请求头
312///
313/// # 返回
314///
315/// - `true`:`Accept` 头存在且包含 `json` 子串
316/// - `false`:`Accept` 头不存在或不包含 `json` 子串
317pub fn is_json_request(headers: &HeaderMap) -> bool {
318    if let Some(accept) = headers.get(header::ACCEPT) {
319        if let Ok(accept_str) = accept.to_str() {
320            // 对齐 PHP `strpos($acceptType, 'json') !== false`
321            // PHP `type()` 方法从 Accept 头解析 MIME 类型,再检查是否包含 "json"
322            // Rust 简化为直接检查 Accept 头是否包含 "json" 子串
323            // (覆盖 application/json、text/json、application/vnd.api+json 等)
324            return accept_str.to_lowercase().contains("json");
325        }
326    }
327    false
328}
329
330/// 默认 JSON 响应(项目主策略:前后端分离)
331///
332/// 将任意 `Value` 序列化为 JSON 响应,Content-Type 为
333/// `application/json; charset=utf-8`,HTTP 状态码 200。
334///
335/// # 项目主策略
336///
337/// 项目采用前后端分离架构,所有 API 响应默认为 JSON 格式。
338/// 控制器方法可直接返回 `Value`,由本函数统一转换为 JSON 响应。
339///
340/// # 参数
341///
342/// - `data`:要序列化的数据(`Value::Object` / `Value::Array` / `Value::String` 等)
343///
344/// # 返回
345///
346/// `Response`,HTTP 200,Content-Type: application/json; charset=utf-8
347#[tracing::instrument(skip(data))]
348pub fn respond(data: &Value) -> Response {
349    let body = data.to_string();
350    (
351        StatusCode::OK,
352        [(header::CONTENT_TYPE, "application/json; charset=utf-8")],
353        body,
354    )
355        .into_response()
356}
357
358/// HTML 响应(兜底场景:模板渲染、PDF/Excel 导出)
359///
360/// Content-Type 为 `text/html; charset=utf-8`,HTTP 状态码 200。
361///
362/// # 参数
363///
364/// - `content`:HTML 内容
365///
366/// # 返回
367///
368/// `Response`,HTTP 200,Content-Type: text/html; charset=utf-8
369#[tracing::instrument(skip(content))]
370pub fn respond_html(content: impl Into<String>) -> Response {
371    (
372        StatusCode::OK,
373        [(header::CONTENT_TYPE, "text/html; charset=utf-8")],
374        content.into(),
375    )
376        .into_response()
377}
378
379/// 纯文本响应(Content-Type: text/plain; charset=utf-8)
380///
381/// 用于调试、健康检查等非 JSON/HTML 场景。
382///
383/// # 参数
384///
385/// - `content`:文本内容
386///
387/// # 返回
388///
389/// `Response`,HTTP 200,Content-Type: text/plain; charset=utf-8
390#[tracing::instrument(skip(content))]
391pub fn respond_text(content: impl Into<String>) -> Response {
392    (
393        StatusCode::OK,
394        [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
395        content.into(),
396    )
397        .into_response()
398}
399
400/// 自动响应(对齐 PHP `Dispatch::autoResponse()`)
401///
402/// 严格对齐 PHP ThinkPHP 6 `autoResponse()` 行为:
403/// 1. 根据 `is_json_request(headers)` 判断响应类型
404/// 2. JSON 请求 → JSON 响应(`respond(data)`)
405/// 3. 非 JSON 请求 → HTML 响应
406///    - `Value::Array` / `Value::Object` → 字面量 `"Array"`(对齐 PHP bug)
407///    - `Value::String` → 字符串内容
408///    - 其他类型 → `data.to_string()`
409///
410/// # PHP bug 复刻说明
411///
412/// PHP `Response::create($data, 'html')` 在 `$data` 为数组时,会通过 `print` 输出
413/// 数组,导致输出字面量 `"Array"`。这是 PHP 的已知行为,本函数严格复刻此 bug
414/// 以保证 R5(PHP/Rust 行为对比)一致性。
415///
416/// # PHP 对齐
417///
418/// ```php
419/// // vendor/topthink/framework/src/think/route/Dispatch.php:96-97
420/// $type     = $this->request->isJson() ? 'json' : 'html';
421/// $response = Response::create($data, $type);
422/// ```
423///
424/// # 参数
425///
426/// - `data`:响应数据
427/// - `headers`:请求头(用于判断 `Accept` 头)
428///
429/// # 返回
430///
431/// - JSON 请求 → JSON 响应
432/// - 非 JSON 请求 → HTML 响应(数组输出字面量 `"Array"`)
433#[tracing::instrument(skip(data, headers))]
434pub fn auto_respond(data: &Value, headers: &HeaderMap) -> Response {
435    if is_json_request(headers) {
436        // 对齐 PHP: $type = 'json'
437        respond(data)
438    } else {
439        // 对齐 PHP: $type = 'html'
440        // PHP bug 复刻:数组/对象输出字面量 "Array"
441        let content = match data {
442            Value::Array(_) | Value::Object(_) => "Array".to_string(),
443            Value::String(s) => s.clone(),
444            Value::Null => String::new(),
445            _ => data.to_string(),
446        };
447        respond_html(content)
448    }
449}
450
451/// JSON 响应包装器(项目主策略:默认 JSON 返回)
452///
453/// 由于 Rust 孤儿规则限制,无法直接为 `serde_json::Value` 实现 `IntoResponse`。
454/// 本 newtype 包装 `Value`,使其可以直接作为 axum handler 返回值,
455/// 默认返回 JSON 响应(Content-Type: application/json; charset=utf-8)。
456///
457/// # 用法
458///
459/// ```ignore
460/// use sz_rust_http_facade::response::JsonResponse;
461/// use serde_json::json;
462///
463/// async fn handler() -> JsonResponse {
464///     JsonResponse(json!({"id": 1, "name": "alice"}))
465/// }
466/// ```
467///
468/// 也可通过 `From<Value>` 转换:
469///
470/// ```ignore
471/// use sz_rust_http_facade::response::JsonResponse;
472/// use serde_json::json;
473///
474/// async fn handler() -> JsonResponse {
475///     json!({"id": 1}).into()
476/// }
477/// ```
478///
479/// # 注意
480///
481/// 此类型始终返回 JSON 响应(项目主策略)。若需根据 Accept 头判断,
482/// 请使用 [`auto_respond`] 或 [`DefaultResponseType::Auto`]。
483#[derive(Debug, Clone)]
484pub struct JsonResponse(pub Value);
485
486impl From<Value> for JsonResponse {
487    fn from(v: Value) -> Self {
488        JsonResponse(v)
489    }
490}
491
492impl IntoResponse for JsonResponse {
493    fn into_response(self) -> Response {
494        respond(&self.0)
495    }
496}
497
498// ============================================================================
499// JSONP 响应 — 对齐 PHP `jsonp()` 返回类型
500//
501// JSONP(JSON with Padding)用于跨域请求,通过 <script> 标签加载。
502// 响应格式:`callbackName({...});`
503// Content-Type: application/javascript; charset=utf-8
504//
505// ## 安全说明
506//
507// - 回调函数名校验:仅允许 `[a-zA-Z0-9_.]` 字符,防止 XSS 注入
508// - 对齐 PHP `Response::create($data, 'jsonp')` 行为
509// ============================================================================
510
511/// 回调函数名校验正则(仅允许字母、数字、下划线、点)
512const JSONP_CALLBACK_PATTERN: &str = r"^[a-zA-Z_][a-zA-Z0-9_.]*$";
513
514/// 校验 JSONP 回调函数名是否合法
515///
516/// 仅允许 `[a-zA-Z_][a-zA-Z0-9_.]*` 格式,防止 XSS 注入。
517///
518/// # 参数
519///
520/// - `callback`:回调函数名
521///
522/// # 返回
523///
524/// - `true`:合法
525/// - `false`:非法(包含特殊字符或为空)
526pub fn is_valid_jsonp_callback(callback: &str) -> bool {
527    if callback.is_empty() || callback.len() > 128 {
528        return false;
529    }
530    regex::Regex::new(JSONP_CALLBACK_PATTERN)
531        .map(|re| re.is_match(callback))
532        .unwrap_or(false)
533}
534
535/// 构建 JSONP 响应(对齐 PHP `json()` + `'jsonp'` 类型)
536///
537/// 将数据序列化为 JSON,包裹在回调函数调用中:`callback({...});`
538/// Content-Type 为 `application/javascript; charset=utf-8`。
539///
540/// # 安全说明
541///
542/// 回调函数名会经过校验([`is_valid_jsonp_callback`]),非法名称将返回 400 错误。
543///
544/// # 参数
545///
546/// - `callback`:回调函数名(如 `handleResponse`)
547/// - `data`:要返回的数据
548///
549/// # 返回
550///
551/// `Response`,HTTP 200,Content-Type: application/javascript; charset=utf-8
552#[tracing::instrument(skip(data))]
553pub fn respond_jsonp(callback: &str, data: &Value) -> Response {
554    if !is_valid_jsonp_callback(callback) {
555        return (
556            StatusCode::BAD_REQUEST,
557            [(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
558            "Invalid JSONP callback name".to_string(),
559        )
560            .into_response();
561    }
562
563    let json_str = data.to_string();
564    let body = format!("{callback}({json_str});");
565
566    (
567        StatusCode::OK,
568        [(
569            header::CONTENT_TYPE,
570            "application/javascript; charset=utf-8",
571        )],
572        body,
573    )
574        .into_response()
575}
576
577/// JSONP 响应包装器
578///
579/// 由于 Rust 孤儿规则限制,无法直接为 `(String, Value)` 实现 `IntoResponse`。
580/// 本 newtype 包装回调函数名和数据,使其可以直接作为 axum handler 返回值。
581///
582/// # 用法
583///
584/// ```ignore
585/// use sz_rust_http_facade::response::JsonpResponse;
586/// use serde_json::json;
587///
588/// async fn handler(callback: String) -> JsonpResponse {
589///     JsonpResponse(callback, json!({"id": 1, "name": "alice"}))
590/// }
591/// ```
592#[derive(Debug, Clone)]
593pub struct JsonpResponse(pub String, pub Value);
594
595impl IntoResponse for JsonpResponse {
596    fn into_response(self) -> Response {
597        respond_jsonp(&self.0, &self.1)
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604    use axum::body::Body;
605    use axum::http::{Method, Request};
606    use http_body_util::BodyExt;
607    use tower::ServiceExt;
608
609    // ====================================================================
610    // ApiResponse 单元测试
611    // ====================================================================
612
613    #[test]
614    fn test_api_response_new() {
615        let resp = ApiResponse::new(1, "ok", Value::Object(Map::new()));
616        assert_eq!(resp.code, 1);
617        assert_eq!(resp.msg, "ok");
618        assert!(resp.data.is_object());
619    }
620
621    #[test]
622    fn test_api_response_success() {
623        let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
624        assert_eq!(resp.code, 1);
625        assert_eq!(resp.msg, "ok");
626        assert_eq!(resp.data["id"], 1);
627    }
628
629    #[test]
630    fn test_api_response_success_empty() {
631        let resp = ApiResponse::success_empty();
632        assert_eq!(resp.code, 1);
633        assert_eq!(resp.msg, "");
634        assert!(resp.data.is_object());
635        assert!(resp.data.as_object().unwrap().is_empty());
636    }
637
638    #[test]
639    fn test_api_response_error() {
640        let resp = ApiResponse::error("参数错误");
641        assert_eq!(resp.code, 0);
642        assert_eq!(resp.msg, "参数错误");
643        assert!(resp.data.is_object());
644    }
645
646    #[test]
647    fn test_api_response_error_with_data() {
648        let resp = ApiResponse::error_with_data("失败", serde_json::json!({"field": "name"}));
649        assert_eq!(resp.code, 0);
650        assert_eq!(resp.msg, "失败");
651        assert_eq!(resp.data["field"], "name");
652    }
653
654    #[test]
655    fn test_api_response_error_with_code() {
656        let resp = ApiResponse::error_with_code(-1, "未登录", Value::Object(Map::new()));
657        assert_eq!(resp.code, -1);
658        assert_eq!(resp.msg, "未登录");
659    }
660
661    #[test]
662    fn test_api_response_to_value_field_order() {
663        let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1}));
664        let value = resp.to_value();
665        let obj = value.as_object().unwrap();
666
667        // 字段顺序必须是 code → msg → data
668        let keys: Vec<&String> = obj.keys().collect();
669        assert_eq!(keys, vec!["code", "msg", "data"]);
670    }
671
672    #[test]
673    fn test_api_response_to_value_content() {
674        let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1, "name": "alice"}));
675        let value = resp.to_value();
676        assert_eq!(value["code"], 1);
677        assert_eq!(value["msg"], "ok");
678        assert_eq!(value["data"]["id"], 1);
679        assert_eq!(value["data"]["name"], "alice");
680    }
681
682    #[test]
683    fn test_api_response_to_json_string() {
684        let resp = ApiResponse::new(1, "ok", serde_json::json!({}));
685        let json_str = resp.to_json_string();
686        // 字段顺序必须是 code → msg → data
687        let expected = r#"{"code":1,"msg":"ok","data":{}}"#;
688        assert_eq!(json_str, expected);
689    }
690
691    #[test]
692    fn test_api_response_to_json_string_with_data() {
693        let resp = ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok");
694        let json_str = resp.to_json_string();
695        let expected = r#"{"code":1,"msg":"ok","data":{"id":1,"name":"alice"}}"#;
696        assert_eq!(json_str, expected);
697    }
698
699    #[test]
700    fn test_api_response_to_json_bytes() {
701        let resp = ApiResponse::new(1, "ok", serde_json::json!({}));
702        let json_bytes = resp.to_json_bytes();
703        let expected = r#"{"code":1,"msg":"ok","data":{}}"#;
704        assert_eq!(json_bytes.as_ref(), expected.as_bytes());
705    }
706
707    #[test]
708    fn test_api_response_to_json_bytes_with_data() {
709        let resp = ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok");
710        let json_bytes = resp.to_json_bytes();
711        let expected = r#"{"code":1,"msg":"ok","data":{"id":1,"name":"alice"}}"#;
712        assert_eq!(json_bytes.as_ref(), expected.as_bytes());
713    }
714
715    #[test]
716    fn test_api_response_to_json_bytes_matches_string() {
717        // to_json_bytes 输出与 to_json_string 逐字节一致
718        let resp = ApiResponse::new(-1, "未登录", serde_json::json!({"token": null}));
719        let json_str = resp.to_json_string();
720        let json_bytes = resp.to_json_bytes();
721        assert_eq!(json_bytes.as_ref(), json_str.as_bytes());
722    }
723
724    #[test]
725    fn test_api_response_to_json_bytes_empty() {
726        let resp = ApiResponse::success_empty();
727        let json_bytes = resp.to_json_bytes();
728        let expected = r#"{"code":1,"msg":"","data":{}}"#;
729        assert_eq!(json_bytes.as_ref(), expected.as_bytes());
730    }
731
732    #[test]
733    fn test_api_response_serialize_via_serde() {
734        let resp = ApiResponse::new(0, "失败", Value::Object(Map::new()));
735        let json_str = serde_json::to_string(&resp).unwrap();
736        assert_eq!(json_str, r#"{"code":0,"msg":"失败","data":{}}"#);
737    }
738
739    #[test]
740    fn test_api_response_clone() {
741        let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
742        let cloned = resp.clone();
743        assert_eq!(cloned.code, resp.code);
744        assert_eq!(cloned.msg, resp.msg);
745        assert_eq!(cloned.data, resp.data);
746    }
747
748    #[test]
749    fn test_api_response_debug_format() {
750        let resp = ApiResponse::new(1, "ok", Value::Object(Map::new()));
751        let debug_str = format!("{resp:?}");
752        assert!(debug_str.contains("ApiResponse"));
753        assert!(debug_str.contains("code: 1"));
754        assert!(debug_str.contains("\"ok\""));
755    }
756
757    // ====================================================================
758    // 便捷函数测试
759    // ====================================================================
760
761    #[test]
762    fn test_render_json_returns_response() {
763        let resp = render_json(1, "ok", serde_json::json!({}));
764        assert_eq!(resp.status(), StatusCode::OK);
765        assert_eq!(
766            resp.headers().get("content-type").unwrap(),
767            "application/json; charset=utf-8"
768        );
769    }
770
771    #[test]
772    fn test_render_success_returns_response() {
773        let resp = render_success(serde_json::json!({"id": 1}), "ok");
774        assert_eq!(resp.status(), StatusCode::OK);
775    }
776
777    #[test]
778    fn test_render_error_returns_response() {
779        let resp = render_error("参数错误");
780        assert_eq!(resp.status(), StatusCode::OK); // 业务错误 HTTP 仍 200
781    }
782
783    #[test]
784    fn test_render_error_with_code_returns_response() {
785        let resp = render_error_with_code(-1, "未登录", serde_json::json!({}));
786        assert_eq!(resp.status(), StatusCode::OK);
787    }
788
789    // ====================================================================
790    // 集成测试:通过 axum Router 验证完整响应
791    // ====================================================================
792
793    #[tokio::test]
794    async fn test_api_response_as_handler_return() {
795        async fn handler() -> ApiResponse {
796            ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok")
797        }
798
799        let router = axum::Router::new().route("/", axum::routing::get(handler));
800        let req = Request::builder()
801            .method(Method::GET)
802            .uri("/")
803            .body(Body::empty())
804            .unwrap();
805        let resp = router.oneshot(req).await.unwrap();
806
807        assert_eq!(resp.status(), StatusCode::OK);
808        assert_eq!(
809            resp.headers().get("content-type").unwrap(),
810            "application/json; charset=utf-8"
811        );
812
813        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
814        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
815        let json: Value = serde_json::from_str(&body_str).unwrap();
816
817        assert_eq!(json["code"], 1);
818        assert_eq!(json["msg"], "ok");
819        assert_eq!(json["data"]["id"], 1);
820        assert_eq!(json["data"]["name"], "alice");
821    }
822
823    #[tokio::test]
824    async fn test_render_error_handler_return() {
825        async fn handler() -> Response {
826            render_error("参数错误")
827        }
828
829        let router = axum::Router::new().route("/", axum::routing::post(handler));
830        let req = Request::builder()
831            .method(Method::POST)
832            .uri("/")
833            .body(Body::empty())
834            .unwrap();
835        let resp = router.oneshot(req).await.unwrap();
836
837        assert_eq!(resp.status(), StatusCode::OK);
838
839        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
840        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
841        let json: Value = serde_json::from_str(&body_str).unwrap();
842
843        assert_eq!(json["code"], 0);
844        assert_eq!(json["msg"], "参数错误");
845        assert!(json["data"].is_object());
846    }
847
848    #[tokio::test]
849    async fn test_response_body_exact_format() {
850        // 严格验证响应体格式:{code,msg,data},与 PHP renderJson 完全一致
851        async fn handler() -> ApiResponse {
852            ApiResponse::success_empty()
853        }
854
855        let router = axum::Router::new().route("/", axum::routing::get(handler));
856        let req = Request::builder()
857            .method(Method::GET)
858            .uri("/")
859            .body(Body::empty())
860            .unwrap();
861        let resp = router.oneshot(req).await.unwrap();
862
863        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
864        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
865        assert_eq!(body_str, r#"{"code":1,"msg":"","data":{}}"#);
866    }
867
868    #[tokio::test]
869    async fn test_response_with_complex_data() {
870        async fn handler() -> ApiResponse {
871            ApiResponse::success(
872                serde_json::json!({
873                    "list": [{"id": 1}, {"id": 2}],
874                    "total": 2,
875                    "page": 1,
876                    "size": 10
877                }),
878                "查询成功",
879            )
880        }
881
882        let router = axum::Router::new().route("/", axum::routing::get(handler));
883        let req = Request::builder()
884            .method(Method::GET)
885            .uri("/")
886            .body(Body::empty())
887            .unwrap();
888        let resp = router.oneshot(req).await.unwrap();
889
890        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
891        let body_str = String::from_utf8(bytes.to_vec()).unwrap();
892        let json: Value = serde_json::from_str(&body_str).unwrap();
893
894        assert_eq!(json["code"], 1);
895        assert_eq!(json["msg"], "查询成功");
896        assert_eq!(json["data"]["total"], 2);
897        assert_eq!(json["data"]["list"][0]["id"], 1);
898        assert_eq!(json["data"]["list"][1]["id"], 2);
899    }
900
901    #[tokio::test]
902    async fn test_response_with_various_error_codes() {
903        // 验证各种错误码(对齐 PHP BaseException)
904        let test_cases = vec![
905            (0, "业务失败"),
906            (-1, "未登录"),
907            (-2, "用户不存在"),
908            (-3, "用户被禁用"),
909            (403, "禁止访问"),
910            (404, "资源不存在"),
911            (422, "参数校验失败"),
912            (500, "数据库错误"),
913        ];
914
915        for (code, msg) in test_cases {
916            let resp = ApiResponse::error_with_code(code, msg, Value::Object(Map::new()));
917            let json_str = resp.to_json_string();
918            let json: Value = serde_json::from_str(&json_str).unwrap();
919            assert_eq!(json["code"], code);
920            assert_eq!(json["msg"], msg);
921        }
922    }
923
924    // ====================================================================
925    // PHP 一致性测试(R5 硬约束:PHP/Rust 行为对比)
926    //
927    // 对比 PHP `SzController::renderJson` / `renderSuccess` / `renderError`
928    // 与 Rust `ApiResponse` / `render_json` / `render_success` / `render_error`
929    // 的行为差异。
930    //
931    // PHP 源码(e:\vue\test\鲜视达\server\app\SzController.php):
932    //   protected function renderJson($code = 1, $msg = '', $data = [])
933    //   {
934    //       return compact('code', 'msg', 'data');
935    //   }
936    //
937    //   protected function renderSuccess($msg = 'success', $data = [])
938    //   {
939    //       return json($this->renderJson(1, $msg, $data));
940    //   }
941    //
942    //   protected function renderError($msg = 'error', $data = [], $code = 0)
943    //   {
944    //       return json($this->renderJson($code, $msg, $data));
945    //   }
946    // ====================================================================
947
948    #[test]
949    fn test_php_consistency_render_json_compact_field_order() {
950        // PHP `renderJson` 通过 `compact('code', 'msg', 'data')` 返回数组,
951        // `compact()` 严格按参数顺序保序序列化:code → msg → data。
952        // Rust 使用 `serde_json::Map`(preserve_order)保证相同顺序。
953        let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1}));
954        let value = resp.to_value();
955        let obj = value.as_object().unwrap();
956        let keys: Vec<&String> = obj.keys().collect();
957        assert_eq!(
958            keys,
959            vec!["code", "msg", "data"],
960            "字段顺序必须为 code → msg → data(对齐 PHP compact())"
961        );
962        assert_eq!(value["code"], 1);
963        assert_eq!(value["msg"], "ok");
964        assert_eq!(value["data"]["id"], 1);
965    }
966
967    #[test]
968    fn test_php_consistency_render_json_default_values() {
969        // PHP `renderJson()` 默认值:$code=1, $msg='', $data=[]
970        // 对齐 PHP:`return compact('code', 'msg', 'data');`
971        let resp = ApiResponse::new(1, "", Value::Object(Map::new()));
972        let json_str = resp.to_json_string();
973        assert_eq!(
974            json_str, r#"{"code":1,"msg":"","data":{}}"#,
975            "默认值必须与 PHP renderJson() 一致:code=1, msg='', data={{}}"
976        );
977    }
978
979    #[test]
980    fn test_php_consistency_render_success_calls_render_json_with_code_1() {
981        // PHP `renderSuccess($msg, $data)` 内部调用 `renderJson(1, $msg, $data)`,
982        // 即 code 必须固定为 1。
983        let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
984        assert_eq!(
985            resp.code, 1,
986            "renderSuccess 必须 code=1(对齐 PHP renderJson(1, ...))"
987        );
988        assert_eq!(resp.msg, "ok");
989        assert_eq!(resp.data["id"], 1);
990
991        // 验证完整 JSON 输出格式
992        let json_str = resp.to_json_string();
993        let expected = r#"{"code":1,"msg":"ok","data":{"id":1}}"#;
994        assert_eq!(json_str, expected);
995    }
996
997    #[test]
998    fn test_php_consistency_render_error_default_code_is_0() {
999        // PHP `renderError($msg = 'error', $data = [], $code = 0)` 默认 $code=0
1000        // 内部调用 `renderJson($code, $msg, $data)`,即默认 code=0
1001        let resp = ApiResponse::error("参数错误");
1002        assert_eq!(
1003            resp.code, 0,
1004            "renderError 默认 code=0(对齐 PHP 默认参数 $code = 0)"
1005        );
1006        assert_eq!(resp.msg, "参数错误");
1007        assert!(
1008            resp.data.is_object(),
1009            "renderError 默认 data 为空对象(对齐 PHP $data = [])"
1010        );
1011
1012        // 验证 HTTP 状态码始终为 200(对齐 PHP json() 响应)
1013        let response = render_error("参数错误");
1014        assert_eq!(response.status(), StatusCode::OK);
1015    }
1016
1017    #[test]
1018    fn test_php_consistency_render_error_with_custom_code_aligns_base_exception() {
1019        // PHP `renderError($msg, $data, $code)` 支持自定义错误码
1020        // PHP BaseException 错误码约定:
1021        //   -1 = 未登录
1022        //   -2 = 用户不存在
1023        //   -3 = 用户被禁用
1024        // Rust 必须能复刻这些错误码
1025        let test_cases = vec![
1026            (-1i32, "未登录"),
1027            (-2, "用户不存在"),
1028            (-3, "用户被禁用"),
1029            (0, "业务失败"),
1030        ];
1031
1032        for (code, msg) in test_cases {
1033            let resp = ApiResponse::error_with_code(code, msg, Value::Object(Map::new()));
1034            let json_str = resp.to_json_string();
1035            let json: Value = serde_json::from_str(&json_str).unwrap();
1036            assert_eq!(
1037                json["code"], code,
1038                "自定义错误码必须与 PHP BaseException 约定一致"
1039            );
1040            assert_eq!(json["msg"], msg);
1041            // data 字段必须存在(对齐 PHP compact('code', 'msg', 'data'))
1042            assert!(json.get("data").is_some(), "data 字段必须存在");
1043        }
1044    }
1045
1046    // ====================================================================
1047    // 前后端分离 JSON 默认返回测试
1048    //
1049    // 测试维度:
1050    // 1. DefaultResponseType 枚举(3 种策略)
1051    // 2. is_json_request(Accept 头判断)
1052    // 3. respond(默认 JSON 响应)
1053    // 4. respond_html(HTML 响应)
1054    // 5. respond_text(纯文本响应)
1055    // 6. auto_respond(PHP autoResponse 对齐 + bug 复刻)
1056    // 7. IntoResponse for Value(Value 直接作为 handler 返回值)
1057    // 8. R5 PHP/Rust 行为对比(autoResponse + isJson + 数组字面量 "Array" bug)
1058    // ====================================================================
1059
1060    // ---------- DefaultResponseType 枚举测试 ----------
1061
1062    #[test]
1063    fn test_default_response_type_default_is_json() {
1064        // 项目主策略:默认为 Json
1065        let t = DefaultResponseType::default();
1066        assert_eq!(t, DefaultResponseType::Json);
1067    }
1068
1069    #[test]
1070    fn test_default_response_type_variants_eq() {
1071        assert_eq!(DefaultResponseType::Json, DefaultResponseType::Json);
1072        assert_ne!(DefaultResponseType::Json, DefaultResponseType::Html);
1073        assert_ne!(DefaultResponseType::Json, DefaultResponseType::Auto);
1074        assert_ne!(DefaultResponseType::Html, DefaultResponseType::Auto);
1075    }
1076
1077    #[test]
1078    fn test_default_response_type_clone_copy() {
1079        let t = DefaultResponseType::Json;
1080        let t2 = t; // Copy
1081        assert_eq!(t, t2);
1082        // DefaultResponseType 实现 Copy,无需 clone
1083        let t3 = t;
1084        assert_eq!(t, t3);
1085    }
1086
1087    #[test]
1088    fn test_default_response_type_debug() {
1089        let debug = format!("{:?}", DefaultResponseType::Json);
1090        assert!(debug.contains("Json"));
1091        let debug = format!("{:?}", DefaultResponseType::Html);
1092        assert!(debug.contains("Html"));
1093        let debug = format!("{:?}", DefaultResponseType::Auto);
1094        assert!(debug.contains("Auto"));
1095    }
1096
1097    #[test]
1098    fn test_default_response_type_respond_json() {
1099        // Json 策略:始终返回 JSON
1100        let headers = HeaderMap::new();
1101        let data = serde_json::json!({"id": 1});
1102        let resp = DefaultResponseType::Json.respond(&data, &headers);
1103        assert_eq!(resp.status(), StatusCode::OK);
1104        assert_eq!(
1105            resp.headers().get("content-type").unwrap(),
1106            "application/json; charset=utf-8"
1107        );
1108    }
1109
1110    #[test]
1111    fn test_default_response_type_respond_html() {
1112        // Html 策略:始终返回 HTML
1113        let headers = HeaderMap::new();
1114        let data = serde_json::json!({"id": 1});
1115        let resp = DefaultResponseType::Html.respond(&data, &headers);
1116        assert_eq!(resp.status(), StatusCode::OK);
1117        assert_eq!(
1118            resp.headers().get("content-type").unwrap(),
1119            "text/html; charset=utf-8"
1120        );
1121    }
1122
1123    #[tokio::test]
1124    async fn test_default_response_type_respond_html_body() {
1125        let headers = HeaderMap::new();
1126        let data = serde_json::json!({"id": 1});
1127        let resp = DefaultResponseType::Html.respond(&data, &headers);
1128        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1129        let body = String::from_utf8(bytes.to_vec()).unwrap();
1130        // HTML 策略将 Value 序列化为字符串
1131        assert_eq!(body, r#"{"id":1}"#);
1132    }
1133
1134    #[tokio::test]
1135    async fn test_default_response_type_respond_auto_with_json_accept() {
1136        // Auto 策略 + Accept: application/json → JSON 响应
1137        let mut headers = HeaderMap::new();
1138        headers.insert("accept", "application/json".parse().unwrap());
1139        let data = serde_json::json!({"id": 1});
1140        let resp = DefaultResponseType::Auto.respond(&data, &headers);
1141        assert_eq!(
1142            resp.headers().get("content-type").unwrap(),
1143            "application/json; charset=utf-8"
1144        );
1145    }
1146
1147    #[tokio::test]
1148    async fn test_default_response_type_respond_auto_with_html_accept() {
1149        // Auto 策略 + Accept: text/html → HTML 响应(数组字面量 "Array" bug 复刻)
1150        let mut headers = HeaderMap::new();
1151        headers.insert("accept", "text/html".parse().unwrap());
1152        let data = serde_json::json!({"id": 1});
1153        let resp = DefaultResponseType::Auto.respond(&data, &headers);
1154        assert_eq!(
1155            resp.headers().get("content-type").unwrap(),
1156            "text/html; charset=utf-8"
1157        );
1158        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1159        let body = String::from_utf8(bytes.to_vec()).unwrap();
1160        // PHP bug 复刻:对象输出字面量 "Array"
1161        assert_eq!(body, "Array");
1162    }
1163
1164    // ---------- is_json_request 测试 ----------
1165
1166    #[test]
1167    fn test_is_json_request_with_application_json() {
1168        let mut headers = HeaderMap::new();
1169        headers.insert("accept", "application/json".parse().unwrap());
1170        assert!(is_json_request(&headers));
1171    }
1172
1173    #[test]
1174    fn test_is_json_request_with_text_json() {
1175        let mut headers = HeaderMap::new();
1176        headers.insert("accept", "text/json".parse().unwrap());
1177        assert!(is_json_request(&headers));
1178    }
1179
1180    #[test]
1181    fn test_is_json_request_with_vnd_api_json() {
1182        let mut headers = HeaderMap::new();
1183        headers.insert("accept", "application/vnd.api+json".parse().unwrap());
1184        assert!(is_json_request(&headers));
1185    }
1186
1187    #[test]
1188    fn test_is_json_request_with_wildcard() {
1189        // Accept: */* 不包含 "json" 子串,应返回 false
1190        let mut headers = HeaderMap::new();
1191        headers.insert("accept", "*/*".parse().unwrap());
1192        assert!(!is_json_request(&headers));
1193    }
1194
1195    #[test]
1196    fn test_is_json_request_with_text_html() {
1197        let mut headers = HeaderMap::new();
1198        headers.insert("accept", "text/html".parse().unwrap());
1199        assert!(!is_json_request(&headers));
1200    }
1201
1202    #[test]
1203    fn test_is_json_request_no_accept_header() {
1204        let headers = HeaderMap::new();
1205        assert!(!is_json_request(&headers));
1206    }
1207
1208    #[test]
1209    fn test_is_json_request_case_insensitive() {
1210        // 大小写不敏感(对齐 PHP strpos 在大小写不敏感场景的行为)
1211        let mut headers = HeaderMap::new();
1212        headers.insert("accept", "APPLICATION/JSON".parse().unwrap());
1213        assert!(is_json_request(&headers));
1214    }
1215
1216    #[test]
1217    fn test_is_json_request_mixed_accept() {
1218        // 浏览器可能发送复杂 Accept 头
1219        let mut headers = HeaderMap::new();
1220        headers.insert(
1221            "accept",
1222            "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
1223                .parse()
1224                .unwrap(),
1225        );
1226        assert!(is_json_request(&headers));
1227    }
1228
1229    // ---------- respond 测试 ----------
1230
1231    #[test]
1232    fn test_respond_returns_json_content_type() {
1233        let data = serde_json::json!({"id": 1});
1234        let resp = respond(&data);
1235        assert_eq!(resp.status(), StatusCode::OK);
1236        assert_eq!(
1237            resp.headers().get("content-type").unwrap(),
1238            "application/json; charset=utf-8"
1239        );
1240    }
1241
1242    #[tokio::test]
1243    async fn test_respond_object_body() {
1244        let data = serde_json::json!({"id": 1, "name": "alice"});
1245        let resp = respond(&data);
1246        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1247        let body = String::from_utf8(bytes.to_vec()).unwrap();
1248        assert_eq!(body, r#"{"id":1,"name":"alice"}"#);
1249    }
1250
1251    #[tokio::test]
1252    async fn test_respond_array_body() {
1253        let data = serde_json::json!([1, 2, 3]);
1254        let resp = respond(&data);
1255        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1256        let body = String::from_utf8(bytes.to_vec()).unwrap();
1257        assert_eq!(body, r#"[1,2,3]"#);
1258    }
1259
1260    #[tokio::test]
1261    async fn test_respond_string_value() {
1262        let data = Value::String("hello".to_string());
1263        let resp = respond(&data);
1264        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1265        let body = String::from_utf8(bytes.to_vec()).unwrap();
1266        // Value::String 序列化为 JSON 字符串(带引号)
1267        assert_eq!(body, r#""hello""#);
1268    }
1269
1270    #[tokio::test]
1271    async fn test_respond_null_value() {
1272        let resp = respond(&Value::Null);
1273        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1274        let body = String::from_utf8(bytes.to_vec()).unwrap();
1275        assert_eq!(body, "null");
1276    }
1277
1278    #[tokio::test]
1279    async fn test_respond_number_value() {
1280        let resp = respond(&serde_json::json!(42));
1281        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1282        let body = String::from_utf8(bytes.to_vec()).unwrap();
1283        assert_eq!(body, "42");
1284    }
1285
1286    #[tokio::test]
1287    async fn test_respond_bool_value() {
1288        let resp = respond(&serde_json::json!(true));
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, "true");
1292    }
1293
1294    // ---------- respond_html 测试 ----------
1295
1296    #[test]
1297    fn test_respond_html_content_type() {
1298        let resp = respond_html("<h1>Hello</h1>");
1299        assert_eq!(resp.status(), StatusCode::OK);
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_respond_html_body() {
1308        let resp = respond_html("<p>test</p>");
1309        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1310        let body = String::from_utf8(bytes.to_vec()).unwrap();
1311        assert_eq!(body, "<p>test</p>");
1312    }
1313
1314    #[tokio::test]
1315    async fn test_respond_html_empty() {
1316        let resp = respond_html("");
1317        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1318        let body = String::from_utf8(bytes.to_vec()).unwrap();
1319        assert_eq!(body, "");
1320    }
1321
1322    #[tokio::test]
1323    async fn test_respond_html_with_unicode() {
1324        let resp = respond_html("<p>你好世界</p>");
1325        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1326        let body = String::from_utf8(bytes.to_vec()).unwrap();
1327        assert_eq!(body, "<p>你好世界</p>");
1328    }
1329
1330    // ---------- respond_text 测试 ----------
1331
1332    #[test]
1333    fn test_respond_text_content_type() {
1334        let resp = respond_text("plain text");
1335        assert_eq!(resp.status(), StatusCode::OK);
1336        assert_eq!(
1337            resp.headers().get("content-type").unwrap(),
1338            "text/plain; charset=utf-8"
1339        );
1340    }
1341
1342    #[tokio::test]
1343    async fn test_respond_text_body() {
1344        let resp = respond_text("OK");
1345        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1346        let body = String::from_utf8(bytes.to_vec()).unwrap();
1347        assert_eq!(body, "OK");
1348    }
1349
1350    // ---------- auto_respond 测试 ----------
1351
1352    #[tokio::test]
1353    async fn test_auto_respond_json_request_with_object() {
1354        // Accept: application/json + 对象 → JSON 响应
1355        let mut headers = HeaderMap::new();
1356        headers.insert("accept", "application/json".parse().unwrap());
1357        let data = serde_json::json!({"id": 1});
1358        let resp = auto_respond(&data, &headers);
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#"{"id":1}"#);
1366    }
1367
1368    #[tokio::test]
1369    async fn test_auto_respond_json_request_with_array() {
1370        let mut headers = HeaderMap::new();
1371        headers.insert("accept", "application/json".parse().unwrap());
1372        let data = serde_json::json!([1, 2, 3]);
1373        let resp = auto_respond(&data, &headers);
1374        assert_eq!(
1375            resp.headers().get("content-type").unwrap(),
1376            "application/json; charset=utf-8"
1377        );
1378        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1379        let body = String::from_utf8(bytes.to_vec()).unwrap();
1380        assert_eq!(body, r#"[1,2,3]"#);
1381    }
1382
1383    #[tokio::test]
1384    async fn test_auto_respond_html_request_with_object_returns_array_literal() {
1385        // PHP bug 复刻:Accept: text/html + 对象 → 字面量 "Array"
1386        let mut headers = HeaderMap::new();
1387        headers.insert("accept", "text/html".parse().unwrap());
1388        let data = serde_json::json!({"id": 1});
1389        let resp = auto_respond(&data, &headers);
1390        assert_eq!(
1391            resp.headers().get("content-type").unwrap(),
1392            "text/html; charset=utf-8"
1393        );
1394        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1395        let body = String::from_utf8(bytes.to_vec()).unwrap();
1396        assert_eq!(body, "Array");
1397    }
1398
1399    #[tokio::test]
1400    async fn test_auto_respond_html_request_with_array_returns_array_literal() {
1401        // PHP bug 复刻:Accept: text/html + 数组 → 字面量 "Array"
1402        let mut headers = HeaderMap::new();
1403        headers.insert("accept", "text/html".parse().unwrap());
1404        let data = serde_json::json!([1, 2, 3]);
1405        let resp = auto_respond(&data, &headers);
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, "Array");
1409    }
1410
1411    #[tokio::test]
1412    async fn test_auto_respond_html_request_with_string_returns_string() {
1413        // Accept: text/html + 字符串 → 字符串内容(非字面量 "Array")
1414        let mut headers = HeaderMap::new();
1415        headers.insert("accept", "text/html".parse().unwrap());
1416        let data = Value::String("hello".to_string());
1417        let resp = auto_respond(&data, &headers);
1418        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1419        let body = String::from_utf8(bytes.to_vec()).unwrap();
1420        assert_eq!(body, "hello");
1421    }
1422
1423    #[tokio::test]
1424    async fn test_auto_respond_html_request_with_null_returns_empty() {
1425        let mut headers = HeaderMap::new();
1426        headers.insert("accept", "text/html".parse().unwrap());
1427        let resp = auto_respond(&Value::Null, &headers);
1428        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1429        let body = String::from_utf8(bytes.to_vec()).unwrap();
1430        assert_eq!(body, "");
1431    }
1432
1433    #[tokio::test]
1434    async fn test_auto_respond_html_request_with_number_returns_number_string() {
1435        let mut headers = HeaderMap::new();
1436        headers.insert("accept", "text/html".parse().unwrap());
1437        let resp = auto_respond(&serde_json::json!(42), &headers);
1438        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1439        let body = String::from_utf8(bytes.to_vec()).unwrap();
1440        assert_eq!(body, "42");
1441    }
1442
1443    #[tokio::test]
1444    async fn test_auto_respond_no_accept_header_returns_html() {
1445        // 无 Accept 头 → 视为非 JSON 请求 → HTML 响应
1446        let headers = HeaderMap::new();
1447        let data = serde_json::json!({"id": 1});
1448        let resp = auto_respond(&data, &headers);
1449        assert_eq!(
1450            resp.headers().get("content-type").unwrap(),
1451            "text/html; charset=utf-8"
1452        );
1453    }
1454
1455    #[tokio::test]
1456    async fn test_auto_respond_wildcard_accept_returns_html() {
1457        // Accept: */* 不包含 "json" → HTML 响应(对齐 PHP isJson() 返回 false)
1458        let mut headers = HeaderMap::new();
1459        headers.insert("accept", "*/*".parse().unwrap());
1460        let data = serde_json::json!({"id": 1});
1461        let resp = auto_respond(&data, &headers);
1462        assert_eq!(
1463            resp.headers().get("content-type").unwrap(),
1464            "text/html; charset=utf-8"
1465        );
1466    }
1467
1468    // ---------- IntoResponse for JsonResponse 测试 ----------
1469
1470    #[tokio::test]
1471    async fn test_json_response_into_response_object() {
1472        async fn handler() -> JsonResponse {
1473            JsonResponse(serde_json::json!({"id": 1, "name": "alice"}))
1474        }
1475
1476        let router = axum::Router::new().route("/", axum::routing::get(handler));
1477        let req = Request::builder()
1478            .method(Method::GET)
1479            .uri("/")
1480            .body(Body::empty())
1481            .unwrap();
1482        let resp = router.oneshot(req).await.unwrap();
1483
1484        assert_eq!(resp.status(), StatusCode::OK);
1485        assert_eq!(
1486            resp.headers().get("content-type").unwrap(),
1487            "application/json; charset=utf-8"
1488        );
1489        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1490        let body = String::from_utf8(bytes.to_vec()).unwrap();
1491        assert_eq!(body, r#"{"id":1,"name":"alice"}"#);
1492    }
1493
1494    #[tokio::test]
1495    async fn test_json_response_into_response_array() {
1496        async fn handler() -> JsonResponse {
1497            JsonResponse(serde_json::json!([1, 2, 3]))
1498        }
1499
1500        let router = axum::Router::new().route("/", axum::routing::get(handler));
1501        let req = Request::builder()
1502            .method(Method::GET)
1503            .uri("/")
1504            .body(Body::empty())
1505            .unwrap();
1506        let resp = router.oneshot(req).await.unwrap();
1507
1508        assert_eq!(
1509            resp.headers().get("content-type").unwrap(),
1510            "application/json; charset=utf-8"
1511        );
1512        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1513        let body = String::from_utf8(bytes.to_vec()).unwrap();
1514        assert_eq!(body, r#"[1,2,3]"#);
1515    }
1516
1517    #[tokio::test]
1518    async fn test_json_response_into_response_string() {
1519        async fn handler() -> JsonResponse {
1520            JsonResponse(Value::String("hello".to_string()))
1521        }
1522
1523        let router = axum::Router::new().route("/", axum::routing::get(handler));
1524        let req = Request::builder()
1525            .method(Method::GET)
1526            .uri("/")
1527            .body(Body::empty())
1528            .unwrap();
1529        let resp = router.oneshot(req).await.unwrap();
1530
1531        // Value::String 通过 JsonResponse 返回 JSON 响应(带引号)
1532        assert_eq!(
1533            resp.headers().get("content-type").unwrap(),
1534            "application/json; charset=utf-8"
1535        );
1536        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1537        let body = String::from_utf8(bytes.to_vec()).unwrap();
1538        assert_eq!(body, r#""hello""#);
1539    }
1540
1541    #[tokio::test]
1542    async fn test_json_response_into_response_null() {
1543        async fn handler() -> JsonResponse {
1544            JsonResponse(Value::Null)
1545        }
1546
1547        let router = axum::Router::new().route("/", axum::routing::get(handler));
1548        let req = Request::builder()
1549            .method(Method::GET)
1550            .uri("/")
1551            .body(Body::empty())
1552            .unwrap();
1553        let resp = router.oneshot(req).await.unwrap();
1554
1555        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1556        let body = String::from_utf8(bytes.to_vec()).unwrap();
1557        assert_eq!(body, "null");
1558    }
1559
1560    #[tokio::test]
1561    async fn test_json_response_into_response_post_handler() {
1562        // 模拟前后端分离典型场景:POST 请求 → 处理 → 返回 JsonResponse
1563        async fn handler() -> JsonResponse {
1564            JsonResponse(serde_json::json!({
1565                "code": 1,
1566                "msg": "success",
1567                "data": {"id": 12345, "status": "paid"}
1568            }))
1569        }
1570
1571        let router = axum::Router::new().route("/api/order", axum::routing::post(handler));
1572        let req = Request::builder()
1573            .method(Method::POST)
1574            .uri("/api/order")
1575            .body(Body::empty())
1576            .unwrap();
1577        let resp = router.oneshot(req).await.unwrap();
1578
1579        assert_eq!(resp.status(), StatusCode::OK);
1580        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1581        let body = String::from_utf8(bytes.to_vec()).unwrap();
1582        let json: Value = serde_json::from_str(&body).unwrap();
1583        assert_eq!(json["code"], 1);
1584        assert_eq!(json["msg"], "success");
1585        assert_eq!(json["data"]["id"], 12345);
1586        assert_eq!(json["data"]["status"], "paid");
1587    }
1588
1589    #[test]
1590    fn test_json_response_from_value() {
1591        // From<Value> 转换测试
1592        let value = serde_json::json!({"id": 1});
1593        let json_resp: JsonResponse = value.clone().into();
1594        assert_eq!(json_resp.0, value);
1595    }
1596
1597    #[test]
1598    fn test_json_response_clone_debug() {
1599        let resp = JsonResponse(serde_json::json!({"id": 1}));
1600        let cloned = resp.clone();
1601        assert_eq!(resp.0, cloned.0);
1602
1603        let debug = format!("{resp:?}");
1604        assert!(debug.contains("JsonResponse"));
1605    }
1606
1607    // ---------- R5 PHP/Rust 行为对比测试 ----------
1608    //
1609    // 对比 PHP ThinkPHP 6 `Dispatch::autoResponse()` + `Request::isJson()` 的行为
1610    //
1611    // PHP 源码:
1612    //   vendor/topthink/framework/src/think/route/Dispatch.php:84-107 autoResponse()
1613    //   vendor/topthink/framework/src/think/Request.php:1557-1562 isJson()
1614    //
1615    // PHP autoResponse 行为:
1616    // 1. $data instanceof Response → 直接返回(Rust: Response → 直接返回)
1617    // 2. $data instanceof ResponseInterface → 转换为 Response
1618    // 3. $data !== null → isJson() ? 'json' : 'html'
1619    //    - isJson=true → JSON 响应(数组 → JSON 编码)
1620    //    - isJson=false → HTML 响应(数组 → 字面量 "Array")
1621    // 4. $data === null → ob_get_clean + html
1622    //
1623    // PHP isJson 行为:
1624    // - 检查 Accept 头是否包含 "json" 子串
1625    // - 大小写敏感(PHP strpos 是大小写敏感的)
1626    // - 注意:PHP type() 方法使用 stristr(大小写不敏感),所以 PHP isJson() 实际是大小写不敏感的
1627    // ----------------------------------------------------------------
1628
1629    #[test]
1630    fn test_r5_php_isjson_accept_application_json() {
1631        // PHP: Accept: application/json → isJson() 返回 true
1632        let mut headers = HeaderMap::new();
1633        headers.insert("accept", "application/json".parse().unwrap());
1634        assert!(
1635            is_json_request(&headers),
1636            "Accept: application/json 时 isJson() 必须返回 true(对齐 PHP)"
1637        );
1638    }
1639
1640    #[test]
1641    fn test_r5_php_isjson_accept_text_html() {
1642        // PHP: Accept: text/html → isJson() 返回 false
1643        let mut headers = HeaderMap::new();
1644        headers.insert("accept", "text/html".parse().unwrap());
1645        assert!(
1646            !is_json_request(&headers),
1647            "Accept: text/html 时 isJson() 必须返回 false(对齐 PHP)"
1648        );
1649    }
1650
1651    #[test]
1652    fn test_r5_php_isjson_accept_wildcard() {
1653        // PHP: Accept: */* → type() 返回 ''(无匹配 MIME)→ isJson() 返回 false
1654        let mut headers = HeaderMap::new();
1655        headers.insert("accept", "*/*".parse().unwrap());
1656        assert!(
1657            !is_json_request(&headers),
1658            "Accept: */* 时 isJson() 必须返回 false(对齐 PHP type() 无匹配 MIME)"
1659        );
1660    }
1661
1662    #[test]
1663    fn test_r5_php_isjson_no_accept_header() {
1664        // PHP: 无 Accept 头 → type() 返回 '' → isJson() 返回 false
1665        let headers = HeaderMap::new();
1666        assert!(
1667            !is_json_request(&headers),
1668            "无 Accept 头时 isJson() 必须返回 false(对齐 PHP)"
1669        );
1670    }
1671
1672    #[tokio::test]
1673    async fn test_r5_php_autoresponse_json_type_with_array() {
1674        // PHP: autoResponse($array) + isJson=true → Response::create($array, 'json')
1675        // → JSON 响应(数组被 json_encode)
1676        let mut headers = HeaderMap::new();
1677        headers.insert("accept", "application/json".parse().unwrap());
1678        let data = serde_json::json!([1, 2, 3]);
1679        let resp = auto_respond(&data, &headers);
1680
1681        // 验证 Content-Type 为 JSON
1682        assert_eq!(
1683            resp.headers().get("content-type").unwrap(),
1684            "application/json; charset=utf-8",
1685            "PHP autoResponse + isJson=true 时必须返回 JSON 类型"
1686        );
1687
1688        // 验证响应体为 JSON 编码的数组
1689        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1690        let body = String::from_utf8(bytes.to_vec()).unwrap();
1691        assert_eq!(
1692            body, "[1,2,3]",
1693            "PHP autoResponse + isJson=true 时数组必须被 json_encode"
1694        );
1695    }
1696
1697    #[tokio::test]
1698    async fn test_r5_php_autoresponse_html_type_with_array_returns_array_literal() {
1699        // PHP bug 复刻:autoResponse($array) + isJson=false → Response::create($array, 'html')
1700        // → PHP `print($array)` 输出字面量 "Array"
1701        // Rust 严格对齐此 bug
1702        let mut headers = HeaderMap::new();
1703        headers.insert("accept", "text/html".parse().unwrap());
1704        let data = serde_json::json!([1, 2, 3]);
1705        let resp = auto_respond(&data, &headers);
1706
1707        // 验证 Content-Type 为 HTML
1708        assert_eq!(
1709            resp.headers().get("content-type").unwrap(),
1710            "text/html; charset=utf-8",
1711            "PHP autoResponse + isJson=false 时必须返回 HTML 类型"
1712        );
1713
1714        // 验证响应体为字面量 "Array"(PHP bug)
1715        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1716        let body = String::from_utf8(bytes.to_vec()).unwrap();
1717        assert_eq!(
1718            body, "Array",
1719            "PHP autoResponse + isJson=false 时数组必须输出字面量 'Array'(PHP bug 复刻)"
1720        );
1721    }
1722
1723    #[tokio::test]
1724    async fn test_r5_php_autoresponse_html_type_with_object_returns_array_literal() {
1725        // PHP bug 复刻:autoResponse($assocArray) + isJson=false
1726        // PHP 中关联数组也是数组,print 输出 "Array"
1727        let mut headers = HeaderMap::new();
1728        headers.insert("accept", "text/html".parse().unwrap());
1729        let data = serde_json::json!({"name": "alice", "age": 30});
1730        let resp = auto_respond(&data, &headers);
1731
1732        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1733        let body = String::from_utf8(bytes.to_vec()).unwrap();
1734        assert_eq!(
1735            body, "Array",
1736            "PHP autoResponse + isJson=false 时关联数组也输出字面量 'Array'(PHP bug 复刻)"
1737        );
1738    }
1739
1740    #[tokio::test]
1741    async fn test_r5_php_autoresponse_html_type_with_string_returns_string() {
1742        // PHP: autoResponse($string) + isJson=false → Response::create($string, 'html')
1743        // → 字符串原样输出
1744        let mut headers = HeaderMap::new();
1745        headers.insert("accept", "text/html".parse().unwrap());
1746        let data = Value::String("Hello World".to_string());
1747        let resp = auto_respond(&data, &headers);
1748
1749        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1750        let body = String::from_utf8(bytes.to_vec()).unwrap();
1751        assert_eq!(
1752            body, "Hello World",
1753            "PHP autoResponse + isJson=false + 字符串时必须原样输出字符串内容"
1754        );
1755    }
1756
1757    #[tokio::test]
1758    async fn test_r5_php_autoresponse_no_accept_header_returns_html() {
1759        // PHP: 无 Accept 头 → isJson=false → HTML 响应
1760        let headers = HeaderMap::new();
1761        let data = serde_json::json!({"id": 1});
1762        let resp = auto_respond(&data, &headers);
1763
1764        assert_eq!(
1765            resp.headers().get("content-type").unwrap(),
1766            "text/html; charset=utf-8",
1767            "无 Accept 头时 PHP isJson() 返回 false,必须返回 HTML 类型"
1768        );
1769    }
1770
1771    #[tokio::test]
1772    async fn test_r5_php_autoresponse_wildcard_accept_returns_html() {
1773        // PHP: Accept: */* → type() 返回 ''(无匹配)→ isJson=false → HTML 响应
1774        let mut headers = HeaderMap::new();
1775        headers.insert("accept", "*/*".parse().unwrap());
1776        let data = serde_json::json!({"id": 1});
1777        let resp = auto_respond(&data, &headers);
1778
1779        assert_eq!(
1780            resp.headers().get("content-type").unwrap(),
1781            "text/html; charset=utf-8",
1782            "Accept: */* 时 PHP isJson() 返回 false,必须返回 HTML 类型"
1783        );
1784    }
1785
1786    #[tokio::test]
1787    async fn test_r5_php_autoresponse_mixed_accept_with_json() {
1788        // PHP: Accept: text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8
1789        // → type() 匹配到 json → isJson=true → JSON 响应
1790        let mut headers = HeaderMap::new();
1791        headers.insert(
1792            "accept",
1793            "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
1794                .parse()
1795                .unwrap(),
1796        );
1797        let data = serde_json::json!({"id": 1});
1798        let resp = auto_respond(&data, &headers);
1799
1800        assert_eq!(
1801            resp.headers().get("content-type").unwrap(),
1802            "application/json; charset=utf-8",
1803            "Accept 头含 json MIME 时 PHP isJson() 返回 true,必须返回 JSON 类型"
1804        );
1805    }
1806
1807    #[test]
1808    fn test_r5_php_isjson_case_insensitive_alignment() {
1809        // PHP type() 方法使用 stristr(大小写不敏感),所以 isJson() 实际是大小写不敏感的
1810        // Rust 实现使用 to_lowercase().contains("json") 对齐此行为
1811        let mut headers_upper = HeaderMap::new();
1812        headers_upper.insert("accept", "APPLICATION/JSON".parse().unwrap());
1813        assert!(
1814            is_json_request(&headers_upper),
1815            "PHP isJson() 大小写不敏感(stristr),Rust 必须对齐"
1816        );
1817
1818        let mut headers_mixed = HeaderMap::new();
1819        headers_mixed.insert("accept", "Application/Json".parse().unwrap());
1820        assert!(
1821            is_json_request(&headers_mixed),
1822            "PHP isJson() 大小写不敏感(stristr),Rust 必须对齐"
1823        );
1824    }
1825
1826    #[tokio::test]
1827    async fn test_r5_php_default_response_type_json_is_project_main_strategy() {
1828        // 项目主策略:前后端分离 JSON 默认返回
1829        // 与 PHP 不同(PHP 依赖 Accept 头),Rust 项目主策略默认返回 JSON
1830        // 这使得即使没有 Accept: application/json 头,也返回 JSON
1831        let headers = HeaderMap::new(); // 无 Accept 头
1832        let data = serde_json::json!({"id": 1, "name": "alice"});
1833
1834        // 使用 DefaultResponseType::Json(项目主策略)
1835        let resp = DefaultResponseType::Json.respond(&data, &headers);
1836        assert_eq!(
1837            resp.headers().get("content-type").unwrap(),
1838            "application/json; charset=utf-8",
1839            "项目主策略:默认返回 JSON,不受 Accept 头影响"
1840        );
1841
1842        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1843        let body = String::from_utf8(bytes.to_vec()).unwrap();
1844        assert_eq!(
1845            body, r#"{"id":1,"name":"alice"}"#,
1846            "项目主策略:默认返回 JSON 编码的内容"
1847        );
1848    }
1849
1850    #[tokio::test]
1851    async fn test_r5_php_json_response_default_json_strategy() {
1852        // 项目主策略:JsonResponse 直接作为 handler 返回值 → 默认 JSON 响应
1853        // 这与 PHP 的 autoResponse 不同(PHP 会检查 Accept 头),
1854        // 但与项目实际开发约定一致(始终使用 renderSuccess/renderError 返回 JSON)
1855        async fn handler() -> JsonResponse {
1856            JsonResponse(serde_json::json!({"code": 1, "msg": "ok", "data": {"id": 1}}))
1857        }
1858
1859        let router = axum::Router::new().route("/", axum::routing::get(handler));
1860
1861        // 即使发送 Accept: text/html,JsonResponse 也返回 JSON(项目主策略)
1862        let req = Request::builder()
1863            .method(Method::GET)
1864            .uri("/")
1865            .header("accept", "text/html")
1866            .body(Body::empty())
1867            .unwrap();
1868        let resp = router.oneshot(req).await.unwrap();
1869
1870        assert_eq!(
1871            resp.headers().get("content-type").unwrap(),
1872            "application/json; charset=utf-8",
1873            "项目主策略:JsonResponse IntoResponse 始终返回 JSON,不受 Accept 头影响"
1874        );
1875
1876        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1877        let body = String::from_utf8(bytes.to_vec()).unwrap();
1878        assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
1879    }
1880
1881    // ====================================================================
1882    // JSONP 响应测试
1883    //
1884    // 对齐 PHP `Response::create($data, 'jsonp')` 行为:
1885    // - 响应格式:`callbackName({...});`
1886    // - Content-Type: application/javascript; charset=utf-8
1887    // - 回调函数名校验:仅允许 `[a-zA-Z_][a-zA-Z0-9_.]*`,防止 XSS 注入
1888    // ====================================================================
1889
1890    // ---------- is_valid_jsonp_callback 校验测试 ----------
1891
1892    #[test]
1893    fn test_is_valid_jsonp_callback_simple_name() {
1894        assert!(is_valid_jsonp_callback("handleResponse"));
1895        assert!(is_valid_jsonp_callback("cb"));
1896        assert!(is_valid_jsonp_callback("a"));
1897    }
1898
1899    #[test]
1900    fn test_is_valid_jsonp_callback_with_underscore() {
1901        assert!(is_valid_jsonp_callback("handle_response"));
1902        assert!(is_valid_jsonp_callback("_cb"));
1903    }
1904
1905    #[test]
1906    fn test_is_valid_jsonp_callback_with_dot() {
1907        assert!(is_valid_jsonp_callback("module.callback"));
1908        assert!(is_valid_jsonp_callback("app.module.handle"));
1909    }
1910
1911    #[test]
1912    fn test_is_valid_jsonp_callback_with_digits() {
1913        assert!(is_valid_jsonp_callback("cb1"));
1914        assert!(is_valid_jsonp_callback("handle123"));
1915    }
1916
1917    #[test]
1918    fn test_is_valid_jsonp_callback_empty_is_invalid() {
1919        assert!(!is_valid_jsonp_callback(""));
1920    }
1921
1922    #[test]
1923    fn test_is_valid_jsonp_callback_starting_with_digit_is_invalid() {
1924        // 首字符必须是字母或下划线
1925        assert!(!is_valid_jsonp_callback("1callback"));
1926        assert!(!is_valid_jsonp_callback("9cb"));
1927    }
1928
1929    #[test]
1930    fn test_is_valid_jsonp_callback_with_special_chars_is_invalid() {
1931        // XSS 注入防御:禁止特殊字符
1932        assert!(!is_valid_jsonp_callback("alert(1)"));
1933        assert!(!is_valid_jsonp_callback("<script>"));
1934        assert!(!is_valid_jsonp_callback("cb;evil()"));
1935        assert!(!is_valid_jsonp_callback("cb'"));
1936        assert!(!is_valid_jsonp_callback("cb\""));
1937        assert!(!is_valid_jsonp_callback("cb-"));
1938        assert!(!is_valid_jsonp_callback("cb+"));
1939        assert!(!is_valid_jsonp_callback("cb space"));
1940    }
1941
1942    #[test]
1943    fn test_is_valid_jsonp_callback_too_long_is_invalid() {
1944        // 超过 128 字符的回调名视为非法(防止缓冲区攻击)
1945        let long_name = "a".repeat(129);
1946        assert!(!is_valid_jsonp_callback(&long_name));
1947        // 刚好 128 字符是合法的
1948        let max_name = "a".repeat(128);
1949        assert!(is_valid_jsonp_callback(&max_name));
1950    }
1951
1952    // ---------- respond_jsonp 响应构建测试 ----------
1953
1954    #[tokio::test]
1955    async fn test_respond_jsonp_basic_format() {
1956        let data = serde_json::json!({"id": 1, "name": "alice"});
1957        let resp = respond_jsonp("handleResponse", &data);
1958
1959        assert_eq!(resp.status(), StatusCode::OK);
1960        assert_eq!(
1961            resp.headers().get("content-type").unwrap(),
1962            "application/javascript; charset=utf-8"
1963        );
1964
1965        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1966        let body = String::from_utf8(bytes.to_vec()).unwrap();
1967        // 格式:callbackName({...});
1968        assert!(body.starts_with("handleResponse("));
1969        assert!(body.ends_with(");"));
1970        // 内部数据为合法 JSON
1971        let json_str = &body["handleResponse(".len()..body.len() - ");".len()];
1972        let json: Value = serde_json::from_str(json_str).unwrap();
1973        assert_eq!(json["id"], 1);
1974        assert_eq!(json["name"], "alice");
1975    }
1976
1977    #[tokio::test]
1978    async fn test_respond_jsonp_with_array_data() {
1979        let data = serde_json::json!([1, 2, 3]);
1980        let resp = respond_jsonp("cb", &data);
1981
1982        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1983        let body = String::from_utf8(bytes.to_vec()).unwrap();
1984        assert_eq!(body, "cb([1,2,3]);");
1985    }
1986
1987    #[tokio::test]
1988    async fn test_respond_jsonp_with_string_data() {
1989        let data = Value::String("hello".to_string());
1990        let resp = respond_jsonp("cb", &data);
1991
1992        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
1993        let body = String::from_utf8(bytes.to_vec()).unwrap();
1994        // 字符串序列化为带引号的 JSON
1995        assert_eq!(body, r#"cb("hello");"#);
1996    }
1997
1998    #[tokio::test]
1999    async fn test_respond_jsonp_with_null_data() {
2000        let resp = respond_jsonp("cb", &Value::Null);
2001
2002        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
2003        let body = String::from_utf8(bytes.to_vec()).unwrap();
2004        assert_eq!(body, "cb(null);");
2005    }
2006
2007    #[tokio::test]
2008    async fn test_respond_jsonp_with_empty_object() {
2009        let data = serde_json::json!({});
2010        let resp = respond_jsonp("cb", &data);
2011
2012        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
2013        let body = String::from_utf8(bytes.to_vec()).unwrap();
2014        assert_eq!(body, "cb({});");
2015    }
2016
2017    #[tokio::test]
2018    async fn test_respond_jsonp_invalid_callback_returns_400() {
2019        let data = serde_json::json!({"id": 1});
2020        let resp = respond_jsonp("alert(1)", &data);
2021
2022        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
2023        assert_eq!(
2024            resp.headers().get("content-type").unwrap(),
2025            "text/plain; charset=utf-8"
2026        );
2027
2028        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
2029        let body = String::from_utf8(bytes.to_vec()).unwrap();
2030        assert_eq!(body, "Invalid JSONP callback name");
2031    }
2032
2033    #[tokio::test]
2034    async fn test_respond_jsonp_empty_callback_returns_400() {
2035        let data = serde_json::json!({"id": 1});
2036        let resp = respond_jsonp("", &data);
2037
2038        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
2039    }
2040
2041    #[tokio::test]
2042    async fn test_respond_jsonp_xss_injection_blocked() {
2043        // 模拟 XSS 注入尝试:通过回调名注入脚本
2044        let data = serde_json::json!({"id": 1});
2045        let malicious_names = vec![
2046            "<script>alert(1)</script>",
2047            "cb;</script><script>alert(1)",
2048            "cb'+alert(1)+'",
2049            "cb\";alert(1);\"",
2050        ];
2051
2052        for name in malicious_names {
2053            let resp = respond_jsonp(name, &data);
2054            assert_eq!(
2055                resp.status(),
2056                StatusCode::BAD_REQUEST,
2057                "恶意回调名必须被拒绝: {name}"
2058            );
2059        }
2060    }
2061
2062    // ---------- JsonpResponse 包装器测试 ----------
2063
2064    #[tokio::test]
2065    async fn test_jsonp_response_wrapper_basic() {
2066        async fn handler() -> JsonpResponse {
2067            JsonpResponse("handleResponse".to_string(), serde_json::json!({"id": 1}))
2068        }
2069
2070        let router = axum::Router::new().route("/", axum::routing::get(handler));
2071        let req = Request::builder()
2072            .method(Method::GET)
2073            .uri("/")
2074            .body(Body::empty())
2075            .unwrap();
2076        let resp = router.oneshot(req).await.unwrap();
2077
2078        assert_eq!(resp.status(), StatusCode::OK);
2079        assert_eq!(
2080            resp.headers().get("content-type").unwrap(),
2081            "application/javascript; charset=utf-8"
2082        );
2083
2084        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
2085        let body = String::from_utf8(bytes.to_vec()).unwrap();
2086        assert_eq!(body, r#"handleResponse({"id":1});"#);
2087    }
2088
2089    #[tokio::test]
2090    async fn test_jsonp_response_wrapper_invalid_callback() {
2091        async fn handler() -> JsonpResponse {
2092            // 非法回调名
2093            JsonpResponse("1invalid".to_string(), serde_json::json!({}))
2094        }
2095
2096        let router = axum::Router::new().route("/", axum::routing::get(handler));
2097        let req = Request::builder()
2098            .method(Method::GET)
2099            .uri("/")
2100            .body(Body::empty())
2101            .unwrap();
2102        let resp = router.oneshot(req).await.unwrap();
2103
2104        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
2105    }
2106
2107    #[test]
2108    fn test_jsonp_response_clone_debug() {
2109        let resp = JsonpResponse("cb".to_string(), serde_json::json!({"id": 1}));
2110        let cloned = resp.clone();
2111        assert_eq!(cloned.0, "cb");
2112        assert_eq!(cloned.1["id"], 1);
2113
2114        let debug = format!("{resp:?}");
2115        assert!(debug.contains("JsonpResponse"));
2116    }
2117}