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