feat: mock配置迁移至JSON格式并修复body匹配

- 将mock配置从YAML格式迁移到JSON格式
- 修复JSON字符串格式body匹配失败问题
- 添加MCP功能模块
- 更新mock-spec.md规范文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 09:43:11 +08:00
parent 061ceff4b8
commit d364307131
42 changed files with 1509 additions and 893 deletions

View File

@@ -88,45 +88,49 @@ impl MockRouter {
}
// D. Body 匹配(根据 Payload 类型智能比较)
if let Some(ref yaml_body) = rule.request.body {
return self.match_body(yaml_body, payload);
if let Some(ref rule_body) = rule.request.body {
return self.match_body(rule_body, payload);
}
true
}
/// Body 匹配逻辑
fn match_body(&self, yaml_body: &serde_json::Value, payload: &Payload) -> bool {
// 将 YAML body 规范化:如果是字符串,尝试解析为 JSON
let normalized_body = normalize_yaml_body(yaml_body);
fn match_body(&self, rule_body: &serde_json::Value, payload: &Payload) -> bool {
match payload {
Payload::Json(actual) => {
// JSON 对象比较
&normalized_body == actual
// 如果 rule_body 是字符串,尝试解析为 JSON 比较
if let Some(rule_str) = rule_body.as_str() {
// 尝试将字符串解析为 JSON
if let Ok(parsed_rule) = serde_json::from_str::<serde_json::Value>(rule_str) {
return &parsed_rule == actual;
}
}
// 直接比较
rule_body == actual
}
Payload::Xml(actual) => {
// XML 字符串比较(规范化后比较)
normalized_body.as_str()
rule_body.as_str()
.map(|expected| normalize_xml(expected) == normalize_xml(actual))
.unwrap_or(false)
}
Payload::Form(actual) => {
// Form 键值对比较(子集匹配)
compare_form_with_yaml(&normalized_body, actual)
compare_form_with_json(rule_body, actual)
}
Payload::Multipart(actual_data) => {
// Multipart 匹配:支持键值对或字段名列表
compare_multipart_with_yaml(&normalized_body, actual_data)
compare_multipart_with_json(rule_body, actual_data)
}
Payload::Text(actual) => {
// 字符串比较(去掉首尾空白)
normalized_body.as_str()
rule_body.as_str()
.map(|expected| expected.trim() == actual.trim())
.unwrap_or_else(|| normalized_body.to_string().trim() == actual.trim())
.unwrap_or_else(|| rule_body.to_string().trim() == actual.trim())
}
Payload::None => {
false // YAML 配置了 body但请求没有 body
false // 配置了 body但请求没有 body
}
}
}
@@ -141,18 +145,6 @@ impl MockRouter {
}
}
/// 规范化 YAML body如果是字符串尝试解析为 JSON
fn normalize_yaml_body(yaml_body: &serde_json::Value) -> serde_json::Value {
if let Some(s) = yaml_body.as_str() {
// 先 trim 去掉前导/尾随空格,再尝试解析为 JSON
let trimmed = s.trim();
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(trimmed) {
return parsed;
}
}
yaml_body.clone()
}
/// 规范化 XML 字符串:去掉声明、多余空白、格式化为紧凑形式
fn normalize_xml(xml: &str) -> String {
let mut result = xml.to_string();
@@ -182,15 +174,15 @@ fn normalize_xml(xml: &str) -> String {
result
}
/// Form 比较:YAML 中的键值对必须是请求的子集
fn compare_form_with_yaml(yaml_body: &serde_json::Value, actual: &HashMap<String, String>) -> bool {
let yaml_map = match yaml_body.as_object() {
/// Form 比较:JSON 中的键值对必须是请求的子集
fn compare_form_with_json(rule_body: &serde_json::Value, actual: &HashMap<String, String>) -> bool {
let rule_map = match rule_body.as_object() {
Some(obj) => obj,
None => return false,
};
for (key, yaml_val) in yaml_map {
let expected = yaml_val.as_str().map(|s| s.to_string()).unwrap_or_else(|| yaml_val.to_string());
for (key, rule_val) in rule_map {
let expected = rule_val.as_str().map(|s| s.to_string()).unwrap_or_else(|| rule_val.to_string());
if actual.get(key) != Some(&expected) {
return false;
}
@@ -199,10 +191,10 @@ fn compare_form_with_yaml(yaml_body: &serde_json::Value, actual: &HashMap<String
}
/// Multipart 比较:对象和数组形式都只匹配字段名是否存在
fn compare_multipart_with_yaml(yaml_body: &serde_json::Value, actual: &HashMap<String, String>) -> bool {
fn compare_multipart_with_json(rule_body: &serde_json::Value, actual: &HashMap<String, String>) -> bool {
// 方式 1对象形式 - 只匹配键名是否存在(忽略值)
if let Some(yaml_map) = yaml_body.as_object() {
for key in yaml_map.keys() {
if let Some(rule_map) = rule_body.as_object() {
for key in rule_map.keys() {
if !actual.contains_key(key) {
return false;
}
@@ -211,9 +203,9 @@ fn compare_multipart_with_yaml(yaml_body: &serde_json::Value, actual: &HashMap<S
}
// 方式 2数组形式 - 只匹配字段名是否存在
if let Some(yaml_array) = yaml_body.as_array() {
for yaml_field in yaml_array {
let field_name = yaml_field.as_str().map(|s| s.to_string()).unwrap_or_else(|| yaml_field.to_string());
if let Some(rule_array) = rule_body.as_array() {
for rule_field in rule_array {
let field_name = rule_field.as_str().map(|s| s.to_string()).unwrap_or_else(|| rule_field.to_string());
if !actual.contains_key(&field_name) {
return false;
}