use std::collections::HashMap; use crate::models::{MockRule, Payload}; pub struct MockRouter { // 索引表:Key 是路径首段(如 "api"),Value 是该段下的所有 Mock 规则 pub index: HashMap>, } impl MockRouter { pub fn new(index: HashMap>) -> Self { Self { index } } /// 核心匹配函数:根据请求信息寻找匹配的 Mock 规则 pub fn match_rule( &self, method: &str, path: &str, queries: &HashMap, headers: &HashMap, payload: &Payload, ) -> Option<&MockRule> { // 1. 提取请求路径的首段作为索引 Key let key = self.extract_first_segment(path); println!("DEBUG: Request Key: '{}', Available Keys: {:?}", key, self.index.keys()); // 2. 从 HashMap 中快速定位候选规则列表 (O(k) 复杂度) if let Some(rules) = self.index.get(&key) { // 3. 在候选集中进行线性深度匹配 for rule in rules { if self.is_match(rule, method, path, queries, headers, payload) { return Some(rule); } } } None } /// 细粒度匹配逻辑 fn is_match( &self, rule: &MockRule, method: &str, path: &str, queries: &HashMap, headers: &HashMap, payload: &Payload, ) -> bool { // A. 基础校验:Method 和 Path 必须完全一致 (忽略末尾斜杠) if rule.request.method.to_uppercase() != method.to_uppercase() { println!("DEBUG: [NAME:{}] Method Mismatch: YAML={}, Req={}", rule.name, rule.request.method, method); return false; } if rule.request.path.trim_end_matches('/') != path.trim_end_matches('/') { println!("DEBUG: [NAME:{}] Path Mismatch: YAML='{}', Req='{}'", rule.name, rule.request.path, path); return false; } println!("DEBUG: [NAME:{}] Method and Path matched! Checking headers...", rule.name); // B. Query 参数校验 (子集匹配原则) if let Some(ref required_queries) = rule.request.query_params { for (key, val) in required_queries { if queries.get(key) != Some(val) { return false; } } } // C. Header 校验 (大小写不敏感,Content-Type 使用前缀匹配) if let Some(ref required_headers) = rule.request.headers { for (key, val) in required_headers { let key_lower = key.to_lowercase(); let matched = headers.iter().any(|(k, v)| { if k.to_lowercase() != key_lower { return false; } // Content-Type 使用前缀匹配(因为可能包含 boundary 等参数) if key_lower == "content-type" { v.to_lowercase().starts_with(&val.to_lowercase()) } else { v == val } }); if !matched { return false; } } } // D. Body 匹配(根据 Payload 类型智能比较) if let Some(ref rule_body) = rule.request.body { return self.match_body(rule_body, payload); } true } /// Body 匹配逻辑 fn match_body(&self, rule_body: &serde_json::Value, payload: &Payload) -> bool { match payload { Payload::Json(actual) => { // 如果 rule_body 是字符串,尝试解析为 JSON 后比较 if let Some(rule_str) = rule_body.as_str() { // 尝试将字符串解析为 JSON if let Ok(parsed_rule) = serde_json::from_str::(rule_str) { return &parsed_rule == actual; } } // 直接比较 rule_body == actual } Payload::Xml(actual) => { // XML 字符串比较(规范化后比较) rule_body.as_str() .map(|expected| normalize_xml(expected) == normalize_xml(actual)) .unwrap_or(false) } Payload::Form(actual) => { // Form 键值对比较(子集匹配) compare_form_with_json(rule_body, actual) } Payload::Multipart(actual_data) => { // Multipart 匹配:支持键值对或字段名列表 compare_multipart_with_json(rule_body, actual_data) } Payload::Text(actual) => { // 字符串比较(去掉首尾空白) rule_body.as_str() .map(|expected| expected.trim() == actual.trim()) .unwrap_or_else(|| rule_body.to_string().trim() == actual.trim()) } Payload::None => { false // 配置了 body,但请求没有 body } } } /// 与 Loader 保持一致的 Key 提取算法 fn extract_first_segment(&self, path: &str) -> String { path.trim_start_matches('/') .split('/') .next() .unwrap_or("root") .to_string() } } /// 规范化 XML 字符串:去掉声明、多余空白、格式化为紧凑形式 fn normalize_xml(xml: &str) -> String { let mut result = xml.to_string(); // 去掉 XML 声明 if let Some(pos) = result.find("?>") { if result[..pos].contains(">() .chunks(1) .map(|c| c[0]) .collect::(); // 分割成行,去掉每行首尾空白,过滤空行 result = result .lines() .map(|line| line.trim()) .filter(|line| !line.is_empty()) .collect::(); result } /// Form 比较:JSON 中的键值对必须是请求的子集 fn compare_form_with_json(rule_body: &serde_json::Value, actual: &HashMap) -> bool { let rule_map = match rule_body.as_object() { Some(obj) => obj, None => return false, }; 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; } } true } /// Multipart 比较:对象和数组形式都只匹配字段名是否存在 fn compare_multipart_with_json(rule_body: &serde_json::Value, actual: &HashMap) -> bool { // 方式 1:对象形式 - 只匹配键名是否存在(忽略值) if let Some(rule_map) = rule_body.as_object() { for key in rule_map.keys() { if !actual.contains_key(key) { return false; } } return true; } // 方式 2:数组形式 - 只匹配字段名是否存在 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; } } return true; } false }