---
phase: 06-errorhandling
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/error.rs
  - src/parser.rs
autonomous: true
requirements:
  - ERR-01
  - ERR-03
tags:
  - rust
  - errors
  - line-number
must_haves:
  truths:
    - "调用方拿到 InvalidFormat 时能看到行号（如 'at line 42'）和原始内容片段"
    - "调用方拿到 InvalidRecordStartLine 时能看到行号"
    - "调用方拿到 IntParseError 时能看到行号"
    - "ParseError 的 FileNotFound 和 IoError 变体不包含行号（按设计不变）"
    - "ParseError 仍可通过 thiserror 自动生成 Display/Debug/Error trait"
    - "par_iter() 不追踪全局行号，错误中的 line_number 为 0（分区扫描特性）"
  artifacts:
    - path: "src/error.rs"
      provides: "ParseError 枚举（含 line_number 字段的 3 个变体）"
      contains: "ParseError::InvalidFormat { raw: String, line_number: u64 }"
    - path: "src/parser.rs"
      provides: "LogIterator 行号追踪 + make_invalid_format_error 接受 line_number"
      contains: "line_number: u64" in LogIterator struct
  key_links:
    - from: "LogIterator::next()"
      to: "ParseError::InvalidFormat"
      via: "make_invalid_format_error 携带 current_line"
      pattern: "make_invalid_format_error\\(first_line, line_number\\)"
    - from: "LogParser::iter()"
      to: "LogIterator"
      via: "field initialization"
      pattern: "line_number: 1"
    - from: "LogParser::par_iter()"
      to: "LogIterator"
      via: "field initialization"
      pattern: "line_number: 0"
---

<objective>
为 `ParseError` 变体（InvalidFormat、InvalidRecordStartLine、IntParseError）添加 `line_number: u64` 字段，并让 `LogIterator` 在迭代过程中追踪文件绝对行号。调用方捕获错误后可直接看到行号，用 grep/文本编辑器定位问题位置。

Purpose: ERR-01 和 ERR-03 要求。D-01 到 D-03、D-06、D-07 锁定决策的具体实现。
Output: `src/error.rs` 中 ParseError 变体携带 line_number，`src/parser.rs` 中 LogIterator 追踪行号并传递给错误构造。
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/06-errorhandling/06-CONTEXT.md
@CLAUDE.md
@src/error.rs
@src/parser.rs
@src/lib.rs
@tests/parser_errors.rs

<interfaces>
<!-- 关键现有 API（从 src/error.rs 和 src/parser.rs 提取），本 plan 要修改这些类型 -->

```rust
// src/error.rs — 当前 ParseError 枚举（5 个变体）
pub enum ParseError {
    InvalidFormat { raw: String },
    FileNotFound { path: String },
    InvalidRecordStartLine { raw: String },
    IntParseError { field: String, value: String, raw: String },
    IoError(String),
}
// 当前 Display 格式：
//   InvalidFormat         → "invalid format | raw: {raw}"
//   InvalidRecordStartLine → "invalid record start line: line does not match expected format | raw: {raw}"
//   IntParseError         → "failed to parse {field} as integer: {value} | raw: {raw}"
//   FileNotFound          → "file not found or inaccessible: {path}"
//   IoError               → "IO error: {0}"

// src/parser.rs lines 176-180 — LogIterator 当前字段
pub struct LogIterator<'a> {
    data: &'a [u8],
    pos: usize,
    encoding: FileEncodingHint,
}

// src/parser.rs lines 93-99 — LogParser::iter() 当前实现
pub fn iter(&self) -> LogIterator<'_> {
    LogIterator {
        data: &self.mmap,
        pos: 0,
        encoding: self.encoding,
    }
}

// src/parser.rs lines 168-173 — par_iter() 内部 LogIterator 构造
LogIterator {
    data: &data[start..end],
    pos: 0,
    encoding,
}

// src/parser.rs lines 473-479 — make_invalid_format_error 当前签名
#[cold]
fn make_invalid_format_error(raw_bytes: &[u8]) -> ParseError {
    ParseError::InvalidFormat {
        raw: String::from_utf8_lossy(raw_bytes).to_string(),
    }
}

// src/parser.rs lines 283-287 — parse_record_with_hint 当前签名
fn parse_record_with_hint<'a>(
    record_bytes: &'a [u8],
    is_multiline: bool,
    encoding_hint: FileEncodingHint,
) -> Result<Sqllog<'a>, ParseError>

// src/parser.rs lines 275-281 — parse_record 当前实现
pub fn parse_record<'a>(record_bytes: &'a [u8]) -> Result<Sqllog<'a>, ParseError> {
    let is_multiline = memchr(b'\n', record_bytes).is_some();
    parse_record_with_hint(record_bytes, is_multiline, FileEncodingHint::Auto)
}

// make_invalid_format_error 调用位置（都在 parse_record_with_hint 内部）：
//   line 316: return Err(make_invalid_format_error(first_line));
//   line 329: return Err(make_invalid_format_error(first_line));
//   line 342: return Err(make_invalid_format_error(first_line));

// LogIterator::next() 中 parse_record_with_hint 的调用位置（line 241）：
//   return Some(parse_record_with_hint(
//       record_slice, is_multiline, self.encoding,
//   ));
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: 修改 ParseError 枚举，为 InvalidFormat/InvalidRecordStartLine/IntParseError 添加 line_number 字段</name>
  <files>src/error.rs</files>
  <read_first>
    - src/error.rs (当前 ParseError 枚举定义，含 5 个变体、thiserror derive、#[error("...")] Display 格式)
    - .planning/phases/06-errorhandling/06-CONTEXT.md (D-06: 验证 Display 消息 + D-07: 保持变体不变、line_number 只追加到需要行号的变体)
  </read_first>
  <action>
    修改 `src/error.rs` 中的 `ParseError` 枚举。按照 D-07，只修改需要行号的 3 个变体，保持 `FileNotFound` 和 `IoError` 不变。按 D-06 要求更新 Display 格式（#[error] 属性字符串）使行号信息清晰。

    具体变更：

    1. `InvalidFormat` 变体：
       - 字段列表改为：`{ raw: String, line_number: u64 }`
       - Display 格式改为：`"invalid format at line {line_number} | raw: {raw}"`

    2. `InvalidRecordStartLine` 变体：
       - 字段列表改为：`{ raw: String, line_number: u64 }`
       - Display 格式改为：`"invalid record start line at line {line_number}: line does not match expected format | raw: {raw}"`

    3. `IntParseError` 变体：
       - 字段列表改为：`{ field: String, value: String, raw: String, line_number: u64 }`
       - Display 格式改为：`"failed to parse {field} as integer at line {line_number}: {value} | raw: {raw}"`

    不允许的改动：
    - 不要修改 `FileNotFound` 变体（保持 `{ path: String }`，Display 不变）
    - 不要修改 `IoError` 变体（保持 `String`，Display 不变）
    - 不要删除或重命名现有变体
    - 不要删除或修改 thiserror derive 宏或 Debug/PartialEq derive

    验证方式：编译通过后，用 `grep` 确认所有 5 个变体的新字段和 Display 格式。
  </action>
  <verify>
    <automated>cargo build 2>&1 | grep -q "error" || echo "BUILD_OK"</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c 'line_number: u64' src/error.rs` 输出为 3（InvalidFormat, InvalidRecordStartLine, IntParseError 各 1）
    - `grep -c 'FileNotFound' src/error.rs` 结果的行中没有 line_number（FileNotFound 不变）
    - `grep -c 'IoError' src/error.rs` 结果的行中没有 line_number（IoError 不变）
    - `grep "invalid format at line" src/error.rs | grep -c "line_number"` 输出为 1
    - `grep "invalid record start line at line" src/error.rs | grep -c "line_number"` 输出为 1
    - `grep "failed to parse" src/error.rs | grep -c "line_number"` 输出为 1
    - `cargo build` 退出码 0
  </acceptance_criteria>
  <done>
    ParseError 的 3 个变体携带 `line_number: u64` 字段，Display 消息包含行号。FileNotFound 和 IoError 保持不变。
  </done>
</task>

<task type="auto">
  <name>Task 2: LogIterator 添加 line_number 字段并实现行号追踪</name>
  <files>src/parser.rs</files>
  <read_first>
    - src/parser.rs (LogIterator 结构体 line 176-180、LogParser::iter() line 93-99、par_iter() line 168-173、next() 方法 line 185-247)
    - .planning/phases/06-errorhandling/06-CONTEXT.md (D-01: line_number 字段 + 递增位置、D-02: 从 1 开始、D-03: 性能影响接受、具体 ideas: par_iter 用 0)
  </read_first>
  <action>
    在 `src/parser.rs` 中修改 LogIterator 结构体、LogParser::iter()、par_iter() 内部构造，以及 next() 方法。

    **A. LogIterator 结构体（line 176-180）：**
    在 `encoding: FileEncodingHint` 行之后新增一行 `line_number: u64,`。

    **B. LogParser::iter()（line 93-99）：**
    在 LogIterator 字面量中添加 `line_number: 1,`（D-02: 文件绝对行号从 1 开始）。

    **C. par_iter() 内部 LogIterator 构造（line 168-173）：**
    在 encoding 字段行后添加 `line_number: 0,`（分区扫描无法维护全局行号，约定 0 表示不可用）。

    **D. LogIterator::next() 方法（line 185-247）：**
    在 `let data = &self.data[self.pos..];` 之后立即添加 `let current_line = self.line_number;`。
    在 `self.pos += next_start;` 之后、`// Trim trailing CR` 注释之前添加行号计数逻辑：
    ```
    每次迭代的 self.pos 增加量是 next_start。需要统计 data[..next_start] 中所有 '\n' 字节的数量，
    将此数量添加到 self.line_number。实现方式：用 .iter().filter(|&&b| b == b'\n').count() 对 data[..next_start] 计数，
    然后 self.line_number += 计数值 as u64。
    ```
    注意：这必须放在 空记录 continue 检查 之前，这样空记录跳过时 line_number 也被正确更新。
    同时将原有的 `parse_record_with_hint` 调用（位于 return Some(...) 内）添加第 4 个参数 `current_line`。

    **E. 添加 doc comment：**
    在 LogIterator 结构体上方添加 `///` 注释，说明 `line_number` 字段的行为，包括 par_iter() 下为 0 的约定。
  </action>
  <verify>
    <automated>cargo build 2>&1 | grep -q "error" || echo "BUILD_OK"</automated>
  </verify>
  <acceptance_criteria>
    - `grep -n "line_number: u64" src/parser.rs` 至少命中 1 行（LogIterator 字段定义）
    - `grep -n "line_number: 1" src/parser.rs` 至少命中 1 行（LogParser::iter() 初始化）
    - `grep -n "line_number: 0" src/parser.rs` 至少命中 1 行（par_iter 内初始化）
    - `grep -n "current_line" src/parser.rs` 至少命中 2 行（保存 + 传递）
    - `grep "line_number" src/parser.rs | grep "+="` 至少命中 1 行（新行计数）
    - `cargo build` 退出码 0
  </acceptance_criteria>
  <done>
    LogIterator 追踪文件绝对行号（从 1 开始），错误发生时 line_number 写入 ParseError。par_iter() 中使用 0（不追踪）。
  </done>
</task>

<task type="auto">
  <name>Task 3: 更新 make_invalid_format_error 和 parse_record_with_hint 签名以传递 line_number</name>
  <files>src/parser.rs</files>
  <read_first>
    - src/parser.rs (make_invalid_format_error 函数 line 473-479、parse_record_with_hint 函数 line 283-450、parse_record 函数 line 275-281)
    - .planning/phases/06-errorhandling/06-CONTEXT.md (D-01: line_number 写入 ParseError 变体)
  </read_first>
  <action>
    在 `src/parser.rs` 中修改三个函数的签名和调用：

    **A. make_invalid_format_error（line 473-479）：**
    签名从 `fn make_invalid_format_error(raw_bytes: &[u8]) -> ParseError`
    改为 `fn make_invalid_format_error(raw_bytes: &[u8], line_number: u64) -> ParseError`。
    构造 InvalidFormat 时添加 `line_number,` 字段赋值。

    **B. parse_record_with_hint signature（line 283）：**
    签名从 `fn parse_record_with_hint<'a>(record_bytes: &'a [u8], is_multiline: bool, encoding_hint: FileEncodingHint) -> Result<Sqllog<'a>, ParseError>`
    改为 `fn parse_record_with_hint<'a>(record_bytes: &'a [u8], is_multiline: bool, encoding_hint: FileEncodingHint, line_number: u64) -> Result<Sqllog<'a>, ParseError>`。

    **C. parse_record_with_hint 内部（3 处 make_invalid_format_error 调用，lines 316/329/342）：**
    每处改为 `make_invalid_format_error(first_line, line_number)`。

    **D. parse_record（line 275-281）：**
    在 parse_record_with_hint 调用中添加第 4 个参数 `0`（直接调用 parse_record 时无法获取行号）。

    **E. LogIterator::next() 中的 parse_record_with_hint 调用（line 241）：**
    已在 Task 2 中添加 `current_line` 作为第 4 个参数，确认传递正确。
  </action>
  <verify>
    <automated>cargo build 2>&1 | grep -q "error" || echo "BUILD_OK"</automated>
  </verify>
  <acceptance_criteria>
    - `grep -n "fn make_invalid_format_error" src/parser.rs | grep "line_number: u64"` 至少命中 1 行
    - `grep -n "fn parse_record_with_hint" src/parser.rs | grep "line_number: u64"` 至少命中 1 行
    - `grep -n "pub fn parse_record" src/parser.rs | grep "parse_record_with_hint"` 确认调用含 `0`
    - 3 处 `make_invalid_format_error` 调用均含 `line_number` 参数
    - `cargo build` 退出码 0
    - `cargo test --lib` 通过（单元测试，非 integration test）
  </acceptance_criteria>
  <done>
    所有错误构造路径接受 line_number 参数。直接调用 `parse_record()` 时 line_number 为 0，
    通过 LogIterator 解析时携带实际行号。编译通过，单元测试通过。
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| 文件内容 -> LogIterator | 用户提供的日志文件经 mmap 映射，LogIterator 从中解析记录；恶意构造的文件可能包含异常多的换行符 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-06-01 | Denial of Service | LogIterator::next() line_number 计数 | accept | line_number 使用 u64，实际文件换行数不可能溢出（地球最大的日志文件 < 2^40 行）；即使溢出，u64 回绕行为也仅产生错误的行号，不影响解析正确性。D-03 明确接受此性能权衡。 |
| T-06-02 | Tampering | par_iter() 分区扫描 | mitigate | 文档注释中明确说明 par_iter() 下 line_number 为 0（不追踪），避免用户误用。 |
| T-06-SC | Tampering | 依赖注入 (thiserror) | mitigate | thiserror 已锁定在 Cargo.lock 中为 2.0.17，不新增依赖。 |
</threat_model>

<verification>
- `cargo build` 编译通过
- `cargo test --lib` 通过
- `cargo clippy --all-targets -- -D warnings` 无新增警告
- `cargo llvm-cov --workspace --all-features --fail-under-lines 90` 覆盖率 >= 90%
</verification>

<success_criteria>
- ParseError 的 InvalidFormat、InvalidRecordStartLine、IntParseError 变体包含 `line_number: u64` 字段
- FileNotFound 和 IoError 变体保持不变（不含 line_number）
- LogIterator 从行号 1 开始计数，每遇到 `'\n'` 递增
- 调用 `parse_record()`（非迭代器直接解析）时 line_number = 0
- par_iter() 内部 LogIterator 的 line_number = 0（文档注释说明不追踪）
- Display 输出包含行号信息
- 现有 parse_record() 调用方的测试不受影响（line_number=0 仅出现在错误上下文中）
</success_criteria>

<output>
After completion, create `.planning/phases/06-errorhandling/06-01-SUMMARY.md` documenting:
- ParseError 变体的新字段和 Display 格式
- LogIterator line_number 追踪的实现细节（计数位置、初始值、par_iter 约定）
- 对现有测试的影响评估（parse_record 的 line_number=0 不影响通过测试）
</output>
