# 深度限制策略设计决策

## 📋 问题

`MaxSchemaDepth` 应该计算**循环引用深度**还是**引用深度**？

---

## 🔍 两种方案对比

### 方案1：循环引用深度

**定义：** 只计算循环引用链的深度

**示例：**
```
A -> B -> C -> A (循环)
深度计算：A(0) -> B(1) -> C(2) -> A(检测到循环，深度=2)
```

**优点：**
- ✅ 更精确：只在真正有循环时才限制
- ✅ 对非循环的深层嵌套不限制（如 A -> B -> C -> D -> E）

**缺点：**
- ❌ 实现复杂：需要追踪循环链，判断哪些引用属于循环
- ❌ 需要区分循环引用和非循环引用
- ❌ 如果循环引用检测失效，可能无法及时阻止

**实现复杂度：** ⭐⭐⭐ 高

---

### 方案2：引用深度（当前实现）

**定义：** 计算所有 $ref 的递归深度

**示例：**
```
A -> B -> C -> D -> E
深度计算：A(0) -> B(1) -> C(2) -> D(3) -> E(4)
如果 MaxSchemaDepth = 3，则在 D 处剪枝
```

**优点：**
- ✅ 实现简单：统一处理所有 $ref
- ✅ 统一限制：同时防止循环引用和过深嵌套
- ✅ 作为循环引用检测的后备防线

**缺点：**
- ⚠️ 可能对非循环的深层嵌套也进行限制
- ⚠️ 如果实际需要深层嵌套，可能被过早剪枝

**实现复杂度：** ⭐ 低

---

## 📊 场景分析

### 场景1：循环引用

**输入：**
```json
{
  "A": {
    "properties": {
      "b": {"$ref": "#/components/schemas/B"}
    }
  },
  "B": {
    "properties": {
      "a": {"$ref": "#/components/schemas/A"}  // 循环
    }
  }
}
```

**方案1（循环引用深度）：**
- A(0) -> B(1) -> A(检测到循环，深度=1)
- 如果 MaxSchemaDepth = 2，允许展开到 B
- 如果 MaxSchemaDepth = 3，允许展开到 A -> B -> A

**方案2（引用深度）：**
- A(0) -> B(1) -> A(检测到循环，但深度=2)
- 如果 MaxSchemaDepth = 2，在第二次访问 A 时剪枝
- 循环引用检测会先触发，深度限制作为后备

**结论：** 两种方案都能处理，但方案2更简单

---

### 场景2：非循环的深层嵌套

**输入：**
```json
{
  "A": {
    "properties": {
      "b": {"$ref": "#/components/schemas/B"}
    }
  },
  "B": {
    "properties": {
      "c": {"$ref": "#/components/schemas/C"}
    }
  },
  "C": {
    "properties": {
      "d": {"$ref": "#/components/schemas/D"}
    }
  },
  "D": {
    "properties": {
      "e": {"$ref": "#/components/schemas/E"}
    }
  }
}
```

**方案1（循环引用深度）：**
- A(0) -> B(1) -> C(2) -> D(3) -> E(4)
- 因为没有循环，不会限制深度
- 可能展开到任意深度

**方案2（引用深度）：**
- A(0) -> B(1) -> C(2) -> D(3) -> E(4)
- 如果 MaxSchemaDepth = 3，在 D 处剪枝
- 限制所有深层嵌套

**结论：** 方案1允许更深嵌套，方案2统一限制

---

## 🎯 设计目标分析

### 当前实现的目标

1. **防止循环引用导致的无限递归**（主要目标）
2. **防止过深的嵌套导致性能问题**（次要目标）
3. **节省 token**（通过剪枝）

### 两种方案的适用性

| 目标 | 方案1（循环引用深度） | 方案2（引用深度） |
|------|---------------------|------------------|
| **防止循环引用** | ✅ 精确 | ✅ 有效（循环引用检测优先） |
| **防止过深嵌套** | ❌ 不限制 | ✅ 统一限制 |
| **实现复杂度** | ❌ 高 | ✅ 低 |
| **性能保护** | ⚠️ 可能过深 | ✅ 统一保护 |

---

## ✅ 推荐方案：引用深度（方案2）

### 理由

1. **实现简单**：统一处理所有 $ref，代码清晰
2. **双重保护**：
   - 循环引用检测（`visitedRefs`）优先触发
   - 深度限制作为后备防线
3. **性能保护**：同时防止循环引用和过深嵌套
4. **符合设计文档**：策略评估中提到的是"深度限制剪枝"，没有区分循环和非循环

### 当前实现分析

**代码逻辑：**
```go
// 1. 优先检查循环引用
if visitedRefs[refPath] {
    return s.pruneSchema(referencedSchema), nil  // 立即剪枝
}

// 2. 检查深度限制（作为后备）
if currentDepth >= MaxSchemaDepth {
    return s.pruneSchema(referencedSchema), nil  // 剪枝
}

// 3. 继续解析（深度 +1）
return s.resolveDollarRef(ctx, refPath, apiSpec, visitedRefs, currentDepth+1)
```

**工作机制：**
- 循环引用检测优先：如果检测到循环，立即剪枝
- 深度限制后备：如果循环引用检测失效，深度限制会阻止无限递归
- 统一限制：所有深层嵌套都会被限制

---

## 📝 设计决策

### 最终选择：引用深度（方案2）

**参数定义：**
```go
const (
    // MaxSchemaDepth 最大 $ref 引用递归深度
    // 作用：
    // 1. 限制非循环的深层嵌套引用（如 A -> B -> C -> D）
    // 2. 作为循环引用的第二道防线（循环引用检测优先触发）
    // 建议值：2-3 层
    MaxSchemaDepth = 2  // 或 3
)
```

**工作机制：**
1. **循环引用检测**（第一道防线）：
   - 使用 `visitedRefs` 检测
   - 检测到循环引用时立即剪枝

2. **深度限制**（第二道防线）：
   - 使用 `MaxSchemaDepth` 限制
   - 限制所有 $ref 的递归深度
   - 防止过深嵌套和循环引用

**优势：**
- ✅ 实现简单，代码清晰
- ✅ 双重保护，更安全
- ✅ 统一处理，易于维护

---

## 🔄 如果选择方案1（循环引用深度）

**需要修改的地方：**
1. 需要追踪循环链，判断哪些引用属于循环
2. 需要区分循环引用和非循环引用
3. 实现复杂度显著增加

**适用场景：**
- 如果实际业务中经常需要深层嵌套（>3层）
- 如果只需要防止循环引用，不需要限制非循环嵌套

---

## 📊 总结

| 维度 | 方案1（循环引用深度） | 方案2（引用深度）⭐ |
|------|---------------------|------------------|
| **实现复杂度** | ❌ 高 | ✅ 低 |
| **防止循环引用** | ✅ 精确 | ✅ 有效 |
| **防止过深嵌套** | ❌ 不限制 | ✅ 统一限制 |
| **代码可维护性** | ⚠️ 中 | ✅ 高 |
| **性能保护** | ⚠️ 可能过深 | ✅ 统一保护 |

**最终建议：** 使用**引用深度（方案2）**，因为：
1. 实现简单，代码清晰
2. 双重保护，更安全
3. 统一处理，易于维护
4. 符合当前实现和设计文档

---

**文档版本：** v1.0
**最后更新：** 2025-12-24
**决策状态：** ✅ 已确定：使用引用深度（方案2）

