use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::Serialize;
use serde_json::{Map, Value};
#[derive(Debug, Clone)]
pub struct ApiResponse {
pub code: i32,
pub msg: String,
pub data: Value,
}
impl ApiResponse {
pub fn new(code: i32, msg: impl Into<String>, data: Value) -> Self {
Self {
code,
msg: msg.into(),
data,
}
}
pub fn success(data: Value, msg: impl Into<String>) -> Self {
Self::new(1, msg, data)
}
pub fn success_empty() -> Self {
Self::success(Value::Object(Map::new()), "")
}
pub fn error(msg: impl Into<String>) -> Self {
Self::new(0, msg, Value::Object(Map::new()))
}
pub fn error_with_data(msg: impl Into<String>, data: Value) -> Self {
Self::new(0, msg, data)
}
pub fn error_with_code(code: i32, msg: impl Into<String>, data: Value) -> Self {
Self::new(code, msg, data)
}
pub fn to_value(&self) -> Value {
let mut map = Map::new();
map.insert("code".to_string(), Value::Number(self.code.into()));
map.insert("msg".to_string(), Value::String(self.msg.clone()));
map.insert("data".to_string(), self.data.clone());
Value::Object(map)
}
pub fn to_json_string(&self) -> String {
self.to_value().to_string()
}
}
impl Serialize for ApiResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_value().serialize(serializer)
}
}
impl IntoResponse for ApiResponse {
fn into_response(self) -> Response {
let body = self.to_json_string();
(
StatusCode::OK,
[(
axum::http::header::CONTENT_TYPE,
"application/json; charset=utf-8",
)],
body,
)
.into_response()
}
}
pub fn render_json(code: i32, msg: impl Into<String>, data: Value) -> Response {
ApiResponse::new(code, msg, data).into_response()
}
pub fn render_success(data: Value, msg: impl Into<String>) -> Response {
ApiResponse::success(data, msg).into_response()
}
pub fn render_error(msg: impl Into<String>) -> Response {
ApiResponse::error(msg).into_response()
}
pub fn render_error_with_code(code: i32, msg: impl Into<String>, data: Value) -> Response {
ApiResponse::error_with_code(code, msg, data).into_response()
}
use axum::http::{header, HeaderMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DefaultResponseType {
#[default]
Json,
Html,
Auto,
}
impl DefaultResponseType {
pub fn respond(&self, data: &Value, headers: &HeaderMap) -> Response {
match self {
DefaultResponseType::Json => respond(data),
DefaultResponseType::Html => respond_html(data.to_string()),
DefaultResponseType::Auto => auto_respond(data, headers),
}
}
}
pub fn is_json_request(headers: &HeaderMap) -> bool {
if let Some(accept) = headers.get(header::ACCEPT) {
if let Ok(accept_str) = accept.to_str() {
return accept_str.to_lowercase().contains("json");
}
}
false
}
pub fn respond(data: &Value) -> Response {
let body = data.to_string();
(
StatusCode::OK,
[(header::CONTENT_TYPE, "application/json; charset=utf-8")],
body,
)
.into_response()
}
pub fn respond_html(content: impl Into<String>) -> Response {
(
StatusCode::OK,
[(header::CONTENT_TYPE, "text/html; charset=utf-8")],
content.into(),
)
.into_response()
}
pub fn respond_text(content: impl Into<String>) -> Response {
(
StatusCode::OK,
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
content.into(),
)
.into_response()
}
pub fn auto_respond(data: &Value, headers: &HeaderMap) -> Response {
if is_json_request(headers) {
respond(data)
} else {
let content = match data {
Value::Array(_) | Value::Object(_) => "Array".to_string(),
Value::String(s) => s.clone(),
Value::Null => String::new(),
_ => data.to_string(),
};
respond_html(content)
}
}
#[derive(Debug, Clone)]
pub struct JsonResponse(pub Value);
impl From<Value> for JsonResponse {
fn from(v: Value) -> Self {
JsonResponse(v)
}
}
impl IntoResponse for JsonResponse {
fn into_response(self) -> Response {
respond(&self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Method, Request};
use http_body_util::BodyExt;
use tower::ServiceExt;
#[test]
fn test_api_response_new() {
let resp = ApiResponse::new(1, "ok", Value::Object(Map::new()));
assert_eq!(resp.code, 1);
assert_eq!(resp.msg, "ok");
assert!(resp.data.is_object());
}
#[test]
fn test_api_response_success() {
let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
assert_eq!(resp.code, 1);
assert_eq!(resp.msg, "ok");
assert_eq!(resp.data["id"], 1);
}
#[test]
fn test_api_response_success_empty() {
let resp = ApiResponse::success_empty();
assert_eq!(resp.code, 1);
assert_eq!(resp.msg, "");
assert!(resp.data.is_object());
assert!(resp.data.as_object().unwrap().is_empty());
}
#[test]
fn test_api_response_error() {
let resp = ApiResponse::error("参数错误");
assert_eq!(resp.code, 0);
assert_eq!(resp.msg, "参数错误");
assert!(resp.data.is_object());
}
#[test]
fn test_api_response_error_with_data() {
let resp = ApiResponse::error_with_data("失败", serde_json::json!({"field": "name"}));
assert_eq!(resp.code, 0);
assert_eq!(resp.msg, "失败");
assert_eq!(resp.data["field"], "name");
}
#[test]
fn test_api_response_error_with_code() {
let resp = ApiResponse::error_with_code(-1, "未登录", Value::Object(Map::new()));
assert_eq!(resp.code, -1);
assert_eq!(resp.msg, "未登录");
}
#[test]
fn test_api_response_to_value_field_order() {
let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1}));
let value = resp.to_value();
let obj = value.as_object().unwrap();
let keys: Vec<&String> = obj.keys().collect();
assert_eq!(keys, vec!["code", "msg", "data"]);
}
#[test]
fn test_api_response_to_value_content() {
let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1, "name": "alice"}));
let value = resp.to_value();
assert_eq!(value["code"], 1);
assert_eq!(value["msg"], "ok");
assert_eq!(value["data"]["id"], 1);
assert_eq!(value["data"]["name"], "alice");
}
#[test]
fn test_api_response_to_json_string() {
let resp = ApiResponse::new(1, "ok", serde_json::json!({}));
let json_str = resp.to_json_string();
let expected = r#"{"code":1,"msg":"ok","data":{}}"#;
assert_eq!(json_str, expected);
}
#[test]
fn test_api_response_to_json_string_with_data() {
let resp = ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok");
let json_str = resp.to_json_string();
let expected = r#"{"code":1,"msg":"ok","data":{"id":1,"name":"alice"}}"#;
assert_eq!(json_str, expected);
}
#[test]
fn test_api_response_serialize_via_serde() {
let resp = ApiResponse::new(0, "失败", Value::Object(Map::new()));
let json_str = serde_json::to_string(&resp).unwrap();
assert_eq!(json_str, r#"{"code":0,"msg":"失败","data":{}}"#);
}
#[test]
fn test_api_response_clone() {
let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
let cloned = resp.clone();
assert_eq!(cloned.code, resp.code);
assert_eq!(cloned.msg, resp.msg);
assert_eq!(cloned.data, resp.data);
}
#[test]
fn test_api_response_debug_format() {
let resp = ApiResponse::new(1, "ok", Value::Object(Map::new()));
let debug_str = format!("{resp:?}");
assert!(debug_str.contains("ApiResponse"));
assert!(debug_str.contains("code: 1"));
assert!(debug_str.contains("\"ok\""));
}
#[test]
fn test_render_json_returns_response() {
let resp = render_json(1, "ok", serde_json::json!({}));
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
}
#[test]
fn test_render_success_returns_response() {
let resp = render_success(serde_json::json!({"id": 1}), "ok");
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_render_error_returns_response() {
let resp = render_error("参数错误");
assert_eq!(resp.status(), StatusCode::OK); }
#[test]
fn test_render_error_with_code_returns_response() {
let resp = render_error_with_code(-1, "未登录", serde_json::json!({}));
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_api_response_as_handler_return() {
async fn handler() -> ApiResponse {
ApiResponse::success(serde_json::json!({"id": 1, "name": "alice"}), "ok")
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body_str = String::from_utf8(bytes.to_vec()).unwrap();
let json: Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(json["code"], 1);
assert_eq!(json["msg"], "ok");
assert_eq!(json["data"]["id"], 1);
assert_eq!(json["data"]["name"], "alice");
}
#[tokio::test]
async fn test_render_error_handler_return() {
async fn handler() -> Response {
render_error("参数错误")
}
let router = axum::Router::new().route("/", axum::routing::post(handler));
let req = Request::builder()
.method(Method::POST)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body_str = String::from_utf8(bytes.to_vec()).unwrap();
let json: Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(json["code"], 0);
assert_eq!(json["msg"], "参数错误");
assert!(json["data"].is_object());
}
#[tokio::test]
async fn test_response_body_exact_format() {
async fn handler() -> ApiResponse {
ApiResponse::success_empty()
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body_str = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body_str, r#"{"code":1,"msg":"","data":{}}"#);
}
#[tokio::test]
async fn test_response_with_complex_data() {
async fn handler() -> ApiResponse {
ApiResponse::success(
serde_json::json!({
"list": [{"id": 1}, {"id": 2}],
"total": 2,
"page": 1,
"size": 10
}),
"查询成功",
)
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body_str = String::from_utf8(bytes.to_vec()).unwrap();
let json: Value = serde_json::from_str(&body_str).unwrap();
assert_eq!(json["code"], 1);
assert_eq!(json["msg"], "查询成功");
assert_eq!(json["data"]["total"], 2);
assert_eq!(json["data"]["list"][0]["id"], 1);
assert_eq!(json["data"]["list"][1]["id"], 2);
}
#[tokio::test]
async fn test_response_with_various_error_codes() {
let test_cases = vec![
(0, "业务失败"),
(-1, "未登录"),
(-2, "用户不存在"),
(-3, "用户被禁用"),
(403, "禁止访问"),
(404, "资源不存在"),
(422, "参数校验失败"),
(500, "数据库错误"),
];
for (code, msg) in test_cases {
let resp = ApiResponse::error_with_code(code, msg, Value::Object(Map::new()));
let json_str = resp.to_json_string();
let json: Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(json["code"], code);
assert_eq!(json["msg"], msg);
}
}
#[test]
fn test_php_consistency_render_json_compact_field_order() {
let resp = ApiResponse::new(1, "ok", serde_json::json!({"id": 1}));
let value = resp.to_value();
let obj = value.as_object().unwrap();
let keys: Vec<&String> = obj.keys().collect();
assert_eq!(
keys,
vec!["code", "msg", "data"],
"字段顺序必须为 code → msg → data(对齐 PHP compact())"
);
assert_eq!(value["code"], 1);
assert_eq!(value["msg"], "ok");
assert_eq!(value["data"]["id"], 1);
}
#[test]
fn test_php_consistency_render_json_default_values() {
let resp = ApiResponse::new(1, "", Value::Object(Map::new()));
let json_str = resp.to_json_string();
assert_eq!(
json_str, r#"{"code":1,"msg":"","data":{}}"#,
"默认值必须与 PHP renderJson() 一致:code=1, msg='', data={{}}"
);
}
#[test]
fn test_php_consistency_render_success_calls_render_json_with_code_1() {
let resp = ApiResponse::success(serde_json::json!({"id": 1}), "ok");
assert_eq!(
resp.code, 1,
"renderSuccess 必须 code=1(对齐 PHP renderJson(1, ...))"
);
assert_eq!(resp.msg, "ok");
assert_eq!(resp.data["id"], 1);
let json_str = resp.to_json_string();
let expected = r#"{"code":1,"msg":"ok","data":{"id":1}}"#;
assert_eq!(json_str, expected);
}
#[test]
fn test_php_consistency_render_error_default_code_is_0() {
let resp = ApiResponse::error("参数错误");
assert_eq!(
resp.code, 0,
"renderError 默认 code=0(对齐 PHP 默认参数 $code = 0)"
);
assert_eq!(resp.msg, "参数错误");
assert!(
resp.data.is_object(),
"renderError 默认 data 为空对象(对齐 PHP $data = [])"
);
let response = render_error("参数错误");
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_php_consistency_render_error_with_custom_code_aligns_base_exception() {
let test_cases = vec![
(-1i32, "未登录"),
(-2, "用户不存在"),
(-3, "用户被禁用"),
(0, "业务失败"),
];
for (code, msg) in test_cases {
let resp = ApiResponse::error_with_code(code, msg, Value::Object(Map::new()));
let json_str = resp.to_json_string();
let json: Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(
json["code"], code,
"自定义错误码必须与 PHP BaseException 约定一致"
);
assert_eq!(json["msg"], msg);
assert!(json.get("data").is_some(), "data 字段必须存在");
}
}
#[test]
fn test_default_response_type_default_is_json() {
let t = DefaultResponseType::default();
assert_eq!(t, DefaultResponseType::Json);
}
#[test]
fn test_default_response_type_variants_eq() {
assert_eq!(DefaultResponseType::Json, DefaultResponseType::Json);
assert_ne!(DefaultResponseType::Json, DefaultResponseType::Html);
assert_ne!(DefaultResponseType::Json, DefaultResponseType::Auto);
assert_ne!(DefaultResponseType::Html, DefaultResponseType::Auto);
}
#[test]
fn test_default_response_type_clone_copy() {
let t = DefaultResponseType::Json;
let t2 = t; assert_eq!(t, t2);
let t3 = t;
assert_eq!(t, t3);
}
#[test]
fn test_default_response_type_debug() {
let debug = format!("{:?}", DefaultResponseType::Json);
assert!(debug.contains("Json"));
let debug = format!("{:?}", DefaultResponseType::Html);
assert!(debug.contains("Html"));
let debug = format!("{:?}", DefaultResponseType::Auto);
assert!(debug.contains("Auto"));
}
#[test]
fn test_default_response_type_respond_json() {
let headers = HeaderMap::new();
let data = serde_json::json!({"id": 1});
let resp = DefaultResponseType::Json.respond(&data, &headers);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
}
#[test]
fn test_default_response_type_respond_html() {
let headers = HeaderMap::new();
let data = serde_json::json!({"id": 1});
let resp = DefaultResponseType::Html.respond(&data, &headers);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
}
#[tokio::test]
async fn test_default_response_type_respond_html_body() {
let headers = HeaderMap::new();
let data = serde_json::json!({"id": 1});
let resp = DefaultResponseType::Html.respond(&data, &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"{"id":1}"#);
}
#[tokio::test]
async fn test_default_response_type_respond_auto_with_json_accept() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/json".parse().unwrap());
let data = serde_json::json!({"id": 1});
let resp = DefaultResponseType::Auto.respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
}
#[tokio::test]
async fn test_default_response_type_respond_auto_with_html_accept() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = serde_json::json!({"id": 1});
let resp = DefaultResponseType::Auto.respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "Array");
}
#[test]
fn test_is_json_request_with_application_json() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/json".parse().unwrap());
assert!(is_json_request(&headers));
}
#[test]
fn test_is_json_request_with_text_json() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/json".parse().unwrap());
assert!(is_json_request(&headers));
}
#[test]
fn test_is_json_request_with_vnd_api_json() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/vnd.api+json".parse().unwrap());
assert!(is_json_request(&headers));
}
#[test]
fn test_is_json_request_with_wildcard() {
let mut headers = HeaderMap::new();
headers.insert("accept", "*/*".parse().unwrap());
assert!(!is_json_request(&headers));
}
#[test]
fn test_is_json_request_with_text_html() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
assert!(!is_json_request(&headers));
}
#[test]
fn test_is_json_request_no_accept_header() {
let headers = HeaderMap::new();
assert!(!is_json_request(&headers));
}
#[test]
fn test_is_json_request_case_insensitive() {
let mut headers = HeaderMap::new();
headers.insert("accept", "APPLICATION/JSON".parse().unwrap());
assert!(is_json_request(&headers));
}
#[test]
fn test_is_json_request_mixed_accept() {
let mut headers = HeaderMap::new();
headers.insert(
"accept",
"text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
.parse()
.unwrap(),
);
assert!(is_json_request(&headers));
}
#[test]
fn test_respond_returns_json_content_type() {
let data = serde_json::json!({"id": 1});
let resp = respond(&data);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
}
#[tokio::test]
async fn test_respond_object_body() {
let data = serde_json::json!({"id": 1, "name": "alice"});
let resp = respond(&data);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"{"id":1,"name":"alice"}"#);
}
#[tokio::test]
async fn test_respond_array_body() {
let data = serde_json::json!([1, 2, 3]);
let resp = respond(&data);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"[1,2,3]"#);
}
#[tokio::test]
async fn test_respond_string_value() {
let data = Value::String("hello".to_string());
let resp = respond(&data);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#""hello""#);
}
#[tokio::test]
async fn test_respond_null_value() {
let resp = respond(&Value::Null);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "null");
}
#[tokio::test]
async fn test_respond_number_value() {
let resp = respond(&serde_json::json!(42));
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "42");
}
#[tokio::test]
async fn test_respond_bool_value() {
let resp = respond(&serde_json::json!(true));
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "true");
}
#[test]
fn test_respond_html_content_type() {
let resp = respond_html("<h1>Hello</h1>");
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
}
#[tokio::test]
async fn test_respond_html_body() {
let resp = respond_html("<p>test</p>");
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "<p>test</p>");
}
#[tokio::test]
async fn test_respond_html_empty() {
let resp = respond_html("");
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "");
}
#[tokio::test]
async fn test_respond_html_with_unicode() {
let resp = respond_html("<p>你好世界</p>");
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "<p>你好世界</p>");
}
#[test]
fn test_respond_text_content_type() {
let resp = respond_text("plain text");
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/plain; charset=utf-8"
);
}
#[tokio::test]
async fn test_respond_text_body() {
let resp = respond_text("OK");
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "OK");
}
#[tokio::test]
async fn test_auto_respond_json_request_with_object() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/json".parse().unwrap());
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"{"id":1}"#);
}
#[tokio::test]
async fn test_auto_respond_json_request_with_array() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/json".parse().unwrap());
let data = serde_json::json!([1, 2, 3]);
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"[1,2,3]"#);
}
#[tokio::test]
async fn test_auto_respond_html_request_with_object_returns_array_literal() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "Array");
}
#[tokio::test]
async fn test_auto_respond_html_request_with_array_returns_array_literal() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = serde_json::json!([1, 2, 3]);
let resp = auto_respond(&data, &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "Array");
}
#[tokio::test]
async fn test_auto_respond_html_request_with_string_returns_string() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = Value::String("hello".to_string());
let resp = auto_respond(&data, &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "hello");
}
#[tokio::test]
async fn test_auto_respond_html_request_with_null_returns_empty() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let resp = auto_respond(&Value::Null, &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "");
}
#[tokio::test]
async fn test_auto_respond_html_request_with_number_returns_number_string() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let resp = auto_respond(&serde_json::json!(42), &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "42");
}
#[tokio::test]
async fn test_auto_respond_no_accept_header_returns_html() {
let headers = HeaderMap::new();
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
}
#[tokio::test]
async fn test_auto_respond_wildcard_accept_returns_html() {
let mut headers = HeaderMap::new();
headers.insert("accept", "*/*".parse().unwrap());
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
}
#[tokio::test]
async fn test_json_response_into_response_object() {
async fn handler() -> JsonResponse {
JsonResponse(serde_json::json!({"id": 1, "name": "alice"}))
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"{"id":1,"name":"alice"}"#);
}
#[tokio::test]
async fn test_json_response_into_response_array() {
async fn handler() -> JsonResponse {
JsonResponse(serde_json::json!([1, 2, 3]))
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"[1,2,3]"#);
}
#[tokio::test]
async fn test_json_response_into_response_string() {
async fn handler() -> JsonResponse {
JsonResponse(Value::String("hello".to_string()))
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#""hello""#);
}
#[tokio::test]
async fn test_json_response_into_response_null() {
async fn handler() -> JsonResponse {
JsonResponse(Value::Null)
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, "null");
}
#[tokio::test]
async fn test_json_response_into_response_post_handler() {
async fn handler() -> JsonResponse {
JsonResponse(serde_json::json!({
"code": 1,
"msg": "success",
"data": {"id": 12345, "status": "paid"}
}))
}
let router = axum::Router::new().route("/api/order", axum::routing::post(handler));
let req = Request::builder()
.method(Method::POST)
.uri("/api/order")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
let json: Value = serde_json::from_str(&body).unwrap();
assert_eq!(json["code"], 1);
assert_eq!(json["msg"], "success");
assert_eq!(json["data"]["id"], 12345);
assert_eq!(json["data"]["status"], "paid");
}
#[test]
fn test_json_response_from_value() {
let value = serde_json::json!({"id": 1});
let json_resp: JsonResponse = value.clone().into();
assert_eq!(json_resp.0, value);
}
#[test]
fn test_json_response_clone_debug() {
let resp = JsonResponse(serde_json::json!({"id": 1}));
let cloned = resp.clone();
assert_eq!(resp.0, cloned.0);
let debug = format!("{resp:?}");
assert!(debug.contains("JsonResponse"));
}
#[test]
fn test_r5_php_isjson_accept_application_json() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/json".parse().unwrap());
assert!(
is_json_request(&headers),
"Accept: application/json 时 isJson() 必须返回 true(对齐 PHP)"
);
}
#[test]
fn test_r5_php_isjson_accept_text_html() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
assert!(
!is_json_request(&headers),
"Accept: text/html 时 isJson() 必须返回 false(对齐 PHP)"
);
}
#[test]
fn test_r5_php_isjson_accept_wildcard() {
let mut headers = HeaderMap::new();
headers.insert("accept", "*/*".parse().unwrap());
assert!(
!is_json_request(&headers),
"Accept: */* 时 isJson() 必须返回 false(对齐 PHP type() 无匹配 MIME)"
);
}
#[test]
fn test_r5_php_isjson_no_accept_header() {
let headers = HeaderMap::new();
assert!(
!is_json_request(&headers),
"无 Accept 头时 isJson() 必须返回 false(对齐 PHP)"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_json_type_with_array() {
let mut headers = HeaderMap::new();
headers.insert("accept", "application/json".parse().unwrap());
let data = serde_json::json!([1, 2, 3]);
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8",
"PHP autoResponse + isJson=true 时必须返回 JSON 类型"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(
body, "[1,2,3]",
"PHP autoResponse + isJson=true 时数组必须被 json_encode"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_html_type_with_array_returns_array_literal() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = serde_json::json!([1, 2, 3]);
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8",
"PHP autoResponse + isJson=false 时必须返回 HTML 类型"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(
body, "Array",
"PHP autoResponse + isJson=false 时数组必须输出字面量 'Array'(PHP bug 复刻)"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_html_type_with_object_returns_array_literal() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = serde_json::json!({"name": "alice", "age": 30});
let resp = auto_respond(&data, &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(
body, "Array",
"PHP autoResponse + isJson=false 时关联数组也输出字面量 'Array'(PHP bug 复刻)"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_html_type_with_string_returns_string() {
let mut headers = HeaderMap::new();
headers.insert("accept", "text/html".parse().unwrap());
let data = Value::String("Hello World".to_string());
let resp = auto_respond(&data, &headers);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(
body, "Hello World",
"PHP autoResponse + isJson=false + 字符串时必须原样输出字符串内容"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_no_accept_header_returns_html() {
let headers = HeaderMap::new();
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8",
"无 Accept 头时 PHP isJson() 返回 false,必须返回 HTML 类型"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_wildcard_accept_returns_html() {
let mut headers = HeaderMap::new();
headers.insert("accept", "*/*".parse().unwrap());
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"text/html; charset=utf-8",
"Accept: */* 时 PHP isJson() 返回 false,必须返回 HTML 类型"
);
}
#[tokio::test]
async fn test_r5_php_autoresponse_mixed_accept_with_json() {
let mut headers = HeaderMap::new();
headers.insert(
"accept",
"text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
.parse()
.unwrap(),
);
let data = serde_json::json!({"id": 1});
let resp = auto_respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8",
"Accept 头含 json MIME 时 PHP isJson() 返回 true,必须返回 JSON 类型"
);
}
#[test]
fn test_r5_php_isjson_case_insensitive_alignment() {
let mut headers_upper = HeaderMap::new();
headers_upper.insert("accept", "APPLICATION/JSON".parse().unwrap());
assert!(
is_json_request(&headers_upper),
"PHP isJson() 大小写不敏感(stristr),Rust 必须对齐"
);
let mut headers_mixed = HeaderMap::new();
headers_mixed.insert("accept", "Application/Json".parse().unwrap());
assert!(
is_json_request(&headers_mixed),
"PHP isJson() 大小写不敏感(stristr),Rust 必须对齐"
);
}
#[tokio::test]
async fn test_r5_php_default_response_type_json_is_project_main_strategy() {
let headers = HeaderMap::new(); let data = serde_json::json!({"id": 1, "name": "alice"});
let resp = DefaultResponseType::Json.respond(&data, &headers);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8",
"项目主策略:默认返回 JSON,不受 Accept 头影响"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(
body, r#"{"id":1,"name":"alice"}"#,
"项目主策略:默认返回 JSON 编码的内容"
);
}
#[tokio::test]
async fn test_r5_php_json_response_default_json_strategy() {
async fn handler() -> JsonResponse {
JsonResponse(serde_json::json!({"code": 1, "msg": "ok", "data": {"id": 1}}))
}
let router = axum::Router::new().route("/", axum::routing::get(handler));
let req = Request::builder()
.method(Method::GET)
.uri("/")
.header("accept", "text/html")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json; charset=utf-8",
"项目主策略:JsonResponse IntoResponse 始终返回 JSON,不受 Accept 头影响"
);
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(body, r#"{"code":1,"msg":"ok","data":{"id":1}}"#);
}
}