# Body 匹配优化：基于 Content-Type 的智能解析

## Context

**问题**: 当前只支持 JSON body 匹配，非 JSON 请求无法正确匹配。

**需求**: 根据配置和请求的 Content-Type 智能选择解析方式，支持 JSON、XML、Form、Text 等类型。

---

## 核心设计

### Body 解析规则（优先级）

| 优先级 | YAML Content-Type | 请求 Content-Type | 解析方式 |
|--------|-------------------|-------------------|----------|
| 1 | 有 | 任意 | 按 **YAML** 的类型解析 |
| 2 | 无 | 有 | 按 **请求**的 Content-Type 解析 |
| 3 | 无 | 无 | **字符串**比较 |

### 支持的 Content-Type

| Content-Type | 解析结果 |
|--------------|----------|
| `application/json` | `ParsedBody::Json(Value)` |
| `application/xml`, `text/xml` | `ParsedBody::Xml(String)` |
| `application/x-www-form-urlencoded` | `ParsedBody::Form(HashMap)` |
| `multipart/form-data` | `ParsedBody::Multipart(Vec<String>)` |
| `text/plain` 或其他 | `ParsedBody::Text(String)` |

---

## 实现方案

### Step 1: 新增数据结构 (model.rs)

```rust
/// 解析后的请求 Body
#[derive(Debug, Clone)]
pub enum ParsedBody {
    Json(serde_json::Value),
    Xml(String),
    Form(HashMap<String, String>),
    Multipart(Vec<String>),  // 字段名列表
    Text(String),
    None,
}

impl ParsedBody {
    /// 转换为字符串（用于兜底比较）
    pub fn to_compare_string(&self) -> String {
        match self {
            ParsedBody::Json(v) => v.to_string(),
            ParsedBody::Xml(s) | ParsedBody::Text(s) => s.clone(),
            ParsedBody::Form(map) => {
                let mut pairs: Vec<_> = map.iter().collect();
                pairs.sort_by_key(|(k, _)| *k);
                pairs.iter().map(|(k, v)| format!("{}={}", k, v)).join("&")
            }
            ParsedBody::Multipart(fields) => fields.join(","),
            ParsedBody::None => String::new(),
        }
    }
}
```

### Step 2: 解析函数 (handler.rs)

```rust
/// 提取 Content-Type（去掉参数部分）
fn extract_content_type(headers: &HashMap<String, String>) -> Option<String> {
    headers.iter()
        .find(|(k, _)| k.to_lowercase() == "content-type")
        .map(|(_, v)| v.split(';').next().unwrap_or(v).trim().to_lowercase())
}

/// 根据 Content-Type 解析 Body
fn parse_body(content_type: Option<&str>, bytes: &[u8]) -> ParsedBody {
    if bytes.is_empty() {
        return ParsedBody::None;
    }

    match content_type {
        Some(ct) if ct.contains("application/json") => {
            serde_json::from_slice(bytes)
                .map(ParsedBody::Json)
                .unwrap_or_else(|_| ParsedBody::Text(String::from_utf8_lossy(bytes).to_string()))
        }
        Some(ct) if ct.contains("xml") => {
            ParsedBody::Xml(String::from_utf8_lossy(bytes).to_string())
        }
        Some(ct) if ct.contains("form-urlencoded") => {
            ParsedBody::Form(parse_urlencoded(bytes))
        }
        Some(ct) if ct.contains("multipart/form-data") => {
            ParsedBody::Multipart(extract_multipart_fields(bytes))
        }
        _ => {
            ParsedBody::Text(String::from_utf8_lossy(bytes).to_string())
        }
    }
}
```

### Step 3: 确定解析类型 (handler.rs)

```rust
// 在 mock_handler 中：

// 1. 提取请求的 Content-Type
let req_content_type = extract_content_type(&req_headers);

// 2. 读取请求 body
let body_bytes = ...;
let parsed_body = parse_body(req_content_type.as_deref(), &body_bytes);

// 3. 匹配时传递 req_headers 和 parsed_body
// router 会根据 YAML 中是否配置了 Content-Type 来决定使用哪个类型
```

### Step 4: 匹配逻辑 (router.rs)

```rust
fn match_body(
    &self,
    rule: &MockRule,
    parsed_body: &ParsedBody,
    req_content_type: Option<&str>,
) -> bool {
    let yaml_body = match &rule.request.body {
        Some(b) => b,
        None => return true,  // YAML 没配置 body，跳过检查
    };

    // 确定用于解析/比较的 Content-Type
    let effective_content_type = rule.request.headers
        .as_ref()
        .and_then(|h| h.iter()
            .find(|(k, _)| k.to_lowercase() == "content-type")
            .map(|(_, v)| v.as_str()))
        .or(req_content_type);  // YAML 优先，没有则用请求的

    match effective_content_type {
        Some(ct) if ct.contains("application/json") => {
            // JSON 比较
            match parsed_body {
                ParsedBody::Json(actual) => yaml_body == actual,
                _ => false,
            }
        }
        Some(ct) if ct.contains("xml") => {
            // XML 字符串比较
            match parsed_body {
                ParsedBody::Xml(actual) => yaml_body.as_str()
                    .map(|expected| expected.trim() == actual.trim())
                    .unwrap_or(false),
                _ => false,
            }
        }
        Some(ct) if ct.contains("form-urlencoded") => {
            // Form 比较
            match parsed_body {
                ParsedBody::Form(actual) => compare_form(yaml_body, actual),
                _ => false,
            }
        }
        _ => {
            // 无 Content-Type 或其他类型：字符串比较
            let actual_str = parsed_body.to_compare_string();
            let expected_str = yaml_body.to_string();
            expected_str.trim() == actual_str.trim()
        }
    }
}
```

---

## 需要修改的文件

| 文件 | 改动 |
|------|------|
| `src/model.rs` | 新增 `ParsedBody` 枚举和 `to_compare_string()` 方法 |
| `src/handler.rs` | 新增 `extract_content_type()`、`parse_body()`、辅助解析函数 |
| `src/router.rs` | 新增 `match_body()` 方法，调整 `is_match()` 调用 |
| `Cargo.toml` | 可能需要 `urlencoding` 依赖（form 解码） |

---

## YAML 配置示例

```yaml
# JSON 匹配（指定 Content-Type）
- id: login-json
  request:
    method: POST
    path: /api/login
    headers:
      Content-Type: application/json
    body:
      username: admin

# XML 匹配
- id: login-xml
  request:
    method: POST
    path: /api/login
    headers:
      Content-Type: application/xml
    body: "<user><name>admin</name></user>"

# Form 匹配
- id: login-form
  request:
    method: POST
    path: /api/login
    headers:
      Content-Type: application/x-www-form-urlencoded
    body:
      username: admin

# 字符串匹配（无 Content-Type）
- id: echo
  request:
    method: POST
    path: /api/echo
    body: "hello world"
```

---

## 验证

1. `cargo test`
2. 手动测试各类型：
   - JSON 请求 + JSON 规则 → 匹配
   - XML 请求 + XML 规则 → 匹配
   - Form 请求 + Form 规则 → 匹配
   - 无 Content-Type → 字符串比较
