feat: 增加request对body字段的支持

- 实现body字段对json语法,yaml语法的支持的支持
- 新增测试用例login_out.yaml
This commit is contained in:
2025-12-28 21:54:34 +08:00
parent 1775d3659d
commit 5f3269bad5
7 changed files with 193 additions and 79 deletions

View File

@@ -5,9 +5,9 @@ use std::collections::HashMap;
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum MockSource {
/// 对应“一接口一文件”模式
/// 对应“一接口一文件”模式
Single(MockRule),
/// 对应“一文件多接口”模式
/// 对应“一文件多接口”模式
Multiple(Vec<MockRule>),
}
@@ -39,6 +39,8 @@ pub struct RequestMatcher {
pub query_params: Option<HashMap<String, String>>,
/// 选填:只有请求包含这些 Header 时才匹配
pub headers: Option<HashMap<String, String>>,
// 修改点:从 String 改为 Option<serde_json::Value>
pub body: Option<serde_json::Value>,
}
/// 响应内容定义

View File

@@ -23,19 +23,37 @@ pub async fn mock_handler(
Query(params): Query<HashMap<String, String>>,
req: Request<Body>,
) -> impl IntoResponse {
let path = req.uri().path();
// let path = req.uri().path();
// 1. 【关键】将需要的数据克隆出来,断开与 req 的借用关系
let path = req.uri().path().to_string();
let method_str = method.as_str().to_string();
// 1. 将 Axum HeaderMap 转换为简单的 HashMap 供 Router 使用
// 2. 现在可以安全地消耗 req 了,因为上面没有指向 req 内部的引用了
let body_bytes = match axum::body::to_bytes(req.into_body(), 10 * 1024 * 1024).await {
Ok(bytes) => bytes,
Err(_) => {
return Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("Read body error"))
.unwrap();
}
};
let incoming_json: Option<serde_json::Value> = serde_json::from_slice(&body_bytes).ok();
let mut req_headers = HashMap::new();
for (name, value) in headers.iter() {
if let Ok(v) = value.to_str() {
req_headers.insert(name.as_str().to_string(), v.to_string());
}
}
// 2. 执行匹配逻辑
if let Some(rule) = state.router.match_rule(method.as_str(), path, &params, &req_headers) {
if let Some(rule) =
state
.router
.match_rule(&method_str, &path, &params, &req_headers, &incoming_json)
{
// 3. 处理模拟延迟
if let Some(ref settings) = rule.settings {
if let Some(delay) = settings.delay_ms {
@@ -49,7 +67,6 @@ pub async fn mock_handler(
// 注入 YAML 定义的 Header
if let Some(ref h) = rule.response.headers {
for (k, v) in h {
// println!("{}:{}",k.clone(), v.clone());
response_builder = response_builder.header(k, v);
@@ -65,23 +82,26 @@ pub async fn mock_handler(
let body = Body::from_stream(stream);
response_builder.body(body).unwrap()
}
Err(_) => {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!("Mock Error: File not found at {}", file_path)))
.unwrap()
}
Err(_) => Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!(
"Mock Error: File not found at {}",
file_path
)))
.unwrap(),
}
} else {
// B. 内联模式:直接返回字符串内容
response_builder.body(Body::from(rule.response.body.clone())).unwrap()
response_builder
.body(Body::from(rule.response.body.clone()))
.unwrap()
}
} else {
println!("请求头{:?}",req_headers.clone());
println!("请求头{:?}", req_headers.clone());
// 匹配失败返回 404
Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("No mock rule matched this request"))
.unwrap()
}
}
}

View File

@@ -18,6 +18,7 @@ impl MockRouter {
path: &str,
queries: &HashMap<String, String>,
headers: &HashMap<String, String>,
incoming_body: &Option<serde_json::Value>, // 修改 1: 增加参数
) -> Option<&MockRule> {
// 1. 提取请求路径的首段作为索引 Key
let key = self.extract_first_segment(path);
@@ -26,7 +27,7 @@ impl MockRouter {
if let Some(rules) = self.index.get(&key) {
// 3. 在候选集中进行线性深度匹配
for rule in rules {
if self.is_match(rule, method, path, queries, headers) {
if self.is_match(rule, method, path, queries, headers,incoming_body) {
return Some(rule);
}
}
@@ -43,6 +44,7 @@ impl MockRouter {
path: &str,
queries: &HashMap<String, String>,
headers: &HashMap<String, String>,
incoming_body: &Option<serde_json::Value>, // 修改 3: 增加参数
) -> bool {
// A. 基础校验Method 和 Path 必须完全一致 (忽略末尾斜杠)
if rule.request.method.to_uppercase() != method.to_uppercase() {
@@ -78,6 +80,27 @@ impl MockRouter {
}
}
}
// D. 智能 Body 全量比对逻辑
if let Some(ref required_val) = rule.request.body {
match incoming_body {
Some(actual_val) => {
// 实现你的想法:尝试将 YAML 中的 String 转换为 Object 再对比
let final_required = if let Some(s) = required_val.as_str() {
// 如果能解析成 JSON就用解析后的对象否则用原始字符串 Value
serde_json::from_str::<serde_json::Value>(s).unwrap_or_else(|_| required_val.clone())
} else {
required_val.clone()
};
// 执行全量相等比对
if final_required != *actual_val {
println!("DEBUG: [ID:{}] Body Mismatch", rule.id);
return false;
}
}
None => return false, // YAML 要求有 Body 但请求为空
}
}
true
}