docs(core): add Chinese comments to error module

Add detailed Chinese comments explaining thiserror derive macros, From trait conversions, and Result type alias.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-11 16:26:24 +08:00
parent c04cc72b55
commit 9082726e47

View File

@@ -1,50 +1,177 @@
// ─── error.rs ─────────────────────────────────────────────────────────────────
// 这个文件定义了整个 log-viewer-core 库使用的错误类型CoreError和一个
// 便捷的 Result 类型别名。
//
// 在 Rust 中,错误处理通常使用 Result<T, E> 类型:
// - Ok(T) 表示操作成功,包含返回值 T。
// - Err(E) 表示操作失败,包含错误信息 E。
//
// 本文件使用 thiserror 库来简化自定义错误类型的定义。
// thiserror 是 Rust 生态中最流行的错误处理辅助库,它通过"派生宏"derive macro
// 自动为枚举生成 Error trait特征的实现代码避免手写大量样板代码。
// ──────────────────────────────────────────────────────────────────────────────
// 引入 PathBuf 类型,用于表示文件路径。
// PathBuf 是拥有所有权的路径类型(类似 String 之于 &str
use std::path::PathBuf;
// ─── CoreError 枚举定义 ──────────────────────────────────────────────────────
// `#[derive(Debug, thiserror::Error)]` 是两个"派生属性"derive attribute
//
// - Debug: 自动生成 Debug trait 的实现,允许用 {:?} 格式化打印错误信息。
// Debug 是 Rust 的内置派生宏,用于调试输出。
//
// - thiserror::Error: 来自 thiserror 库的派生宏。
// 它会为枚举自动实现 std::error::Error trait。
// 这个宏还会读取每个变体上的 `#[error("...")]` 属性,
// 自动生成 Display trait 的实现(即 .to_string() 的行为)。
// 模板字符串中的 {field_name} 会自动替换为对应字段的值。
//
// `pub enum` 定义一个公开的枚举enumeration
// 枚举是一种可以表示多种不同类型值的类型——它的每个"变体"variant
// 可以携带不同的数据。类似于 TypeScript 的 discriminated union 或 C 的 tagged union。
#[derive(Debug, thiserror::Error)]
pub enum CoreError {
// ─── Io 变体I/O 错误(文件读写等)────────────────────────────────────
// `#[error("I/O error: {context}: {source}")]` 定义了该变体的错误信息模板。
// 当调用 .to_string() 或 println!("{err}") 时,会生成类似这样的字符串:
// "I/O error: reading config: No such file or directory"
// 其中 {context} 和 {source} 会被对应字段的值替换。
//
// 注意:{source} 这里调用的是 source 字段的 Display 格式化输出
// (因为 std::io::Error 实现了 Display
#[error("I/O error: {context}: {source}")]
Io {
// `#[source]` 属性标记这个字段是"错误来源"error source
// 这让 thiserror 知道这个字段是底层错误std::error::Error::source()
// 方法会返回该字段的引用。这在错误链error chain中很有用
// 你可以从一个高层错误追溯到最底层的原始错误。
#[source]
// `source: std::io::Error` — 底层的 I/O 错误(如文件不存在、权限不足等)。
// std::io::Error 是 Rust 标准库中的 I/O 错误类型。
source: std::io::Error,
// `context: String` — 错误上下文描述,说明在做什么操作时出错。
// 例如 "reading config"、"opening log file" 等。
context: String,
},
// ─── Parse 变体解析错误JSON 解析失败等)──────────────────────────────
#[error("parse error at line {line}: {source}")]
Parse {
#[source]
// `source: serde_json::Error` — serde_json 库产生的 JSON 解析错误。
source: serde_json::Error,
// `line: u64` — 发生错误的行号u64 是无符号 64 位整数)。
line: u64,
},
// ─── Search 变体:搜索模式错误(无效的正则表达式等)──────────────────────
#[error("invalid search pattern '{pattern}': {reason}")]
Search { pattern: String, reason: String },
// 这个变体没有 #[source] 标记,因为它不包装底层错误,
// 而是直接描述搜索模式本身的问题。
Search {
// `pattern: String` — 用户输入的搜索模式(如正则表达式)。
pattern: String,
// `reason: String` — 模式无效的原因描述。
reason: String,
},
// ─── Index 变体:索引错误(行号越界等)──────────────────────────────────
#[error("index error: {message}")]
Index { message: String },
Index {
// `message: String` — 错误的描述信息。
message: String,
},
// ─── Config 变体:配置文件解析错误 ──────────────────────────────────────
#[error("config error: {source}")]
Config {
#[source]
// `source: toml::de::Error` — toml 库的解析错误(反序列化失败)。
// toml 是一种配置文件格式de 模块是"反序列化"deserialize的缩写。
source: toml::de::Error,
},
// ─── Encoding 变体:编码错误(非 UTF-8 文件)────────────────────────────
#[error("encoding error at line {line}: {bytes:?}")]
Encoding { line: u64, bytes: Vec<u8> },
// 注意 {bytes:?} 中的 :? 是 Debug 格式化(而不是 Display
// Debug 格式化会输出类似 [255, 254] 这样的数组表示,
// 而 Display 格式化对于 Vec<u8> 没有默认实现,所以这里用 :?。
Encoding {
// `line: u64` — 编码错误发生的行号。
line: u64,
// `bytes: Vec<u8>` — 导致编码错误的前若干字节的原始内容。
// 用于调试,帮助用户了解文件中哪些字节不是有效的 UTF-8。
bytes: Vec<u8>,
},
// ─── Watch 变体:文件监控错误(文件变化通知失败等)──────────────────────
#[error("file watch error: {0}")]
// `{0}` 是"位置参数"positional parameter引用元组/新类型变体中第 0 个字段。
// 这里 0 指的是 String 类型的值本身(因为这是"元组变体"风格,不是结构体风格)。
Watch(String),
// ─── FileNotFound 变体:文件未找到 ──────────────────────────────────────
#[error("file not found: {path:?}")]
FileNotFound { path: PathBuf },
// {path:?} 使用 Debug 格式化输出路径,会保留引号,如 "file not found: "/path/to/file""
FileNotFound {
// `path: PathBuf` — 未找到的文件路径。
path: PathBuf,
},
// ─── Other 变体:其他未分类的错误 ──────────────────────────────────────
#[error("{0}")]
// 这是一个"万能"变体,用于包装任意字符串错误信息。
// {0} 直接引用内部的 String 值。
Other(String),
}
// ─── Result 类型别名 ────────────────────────────────────────────────────────
// `pub type Result<T> = std::result::Result<T, CoreError>;` 定义了一个类型别名。
//
// type 关键字用于创建类型别名type alias即给一个已有的类型起一个新名字。
// 类似于 C 的 typedef 或 Go 的 type alias。
//
// 这样做的目的是简化代码——项目中的函数不需要每次都写:
// fn open(path: &Path) -> std::result::Result<FileReader, CoreError>
// 只需要写:
// fn open(path: &Path) -> Result<FileReader>
//
// 因为错误类型已经固定为 CoreError调用者只需关心成功时的返回值类型 T。
// 这是 Rust 项目中非常常见的模式(标准库本身也大量使用,如 io::Result
//
// <T> 是泛型参数generic parameter表示 T 可以是任何类型。
// 例如 Result<FileReader> 展开为 std::result::Result<FileReader, CoreError>
// Result<usize> 展开为 std::result::Result<usize, CoreError>。
pub type Result<T> = std::result::Result<T, CoreError>;
// ─── From 实现:自动类型转换 ────────────────────────────────────────────────
// `impl From<A> for B` 实现了 From trait表示"可以从类型 A 转换为类型 B"。
// 这个 trait 只需要实现一个方法fn from(value: A) -> Self。
//
// 实现 From<A> for B 的好处:
// 1. 可以使用 B::from(a_value) 显式转换。
// 2. 更重要的是Rust 会自动实现反方向的 Into trait
// 即 A 类型可以调用 .into() 方法转换为 B 类型。
// 3. 在函数返回 Result<T, CoreError> 时,可以用 ? 操作符自动将
// 底层错误(如 std::io::Error转换为 CoreError。
//
// 例如:
// let data = std::fs::read(path)?; // 如果 read 返回 Err(io::Error)
// // ? 操作符会自动调用 CoreError::from(io::Error),将 io::Error 转为 CoreError。
//
// 这就是为什么在 file_reader.rs 中,`std::fs::read(path)?` 可以直接使用 ?
// 来将 std::io::Error 自动转换为 CoreError::Io。
// ─── 从 std::io::Error 转换为 CoreError ─────────────────────────────────────
// 当底层 I/O 操作失败时,自动将 std::io::Error 包装为 CoreError::Io。
impl From<std::io::Error> for CoreError {
// `fn from(err: std::io::Error) -> Self` 中的 Self 代表 CoreError 本身。
fn from(err: std::io::Error) -> Self {
// 创建 CoreError::Io 变体,使用默认上下文 "I/O operation"。
// `.into()` 在这里将 &str 转换为 String因为 context 字段类型是 String
// into() 能够工作是因为 Rust 标准库为 &str 实现了 Into<String>。
CoreError::Io {
source: err,
context: "I/O operation".into(),
@@ -52,42 +179,66 @@ impl From<std::io::Error> for CoreError {
}
}
// ─── 从 serde_json::Error 转换为 CoreError ──────────────────────────────────
// 当 JSON 解析失败时,自动将 serde_json::Error 包装为 CoreError::Parse。
impl From<serde_json::Error> for CoreError {
fn from(err: serde_json::Error) -> Self {
CoreError::Parse {
source: err,
line: 0,
line: 0, // 默认行号为 0调用者可以在更高层覆盖。
}
}
}
// ─── 从 toml::de::Error 转换为 CoreError ────────────────────────────────────
// 当 TOML 配置文件解析失败时,自动将错误包装为 CoreError::Config。
impl From<toml::de::Error> for CoreError {
fn from(err: toml::de::Error) -> Self {
CoreError::Config { source: err }
}
}
// ─── 从 notify::Error 转换为 CoreError ──────────────────────────────────────
// 当文件监控file watch出错时自动将 notify 库的错误包装为 CoreError::Watch。
// notify 是 Rust 生态中用于监控文件变化的库(如文件被修改、创建、删除等)。
impl From<notify::Error> for CoreError {
fn from(err: notify::Error) -> Self {
// 将 notify 错误转为字符串后存储,不保留原始错误引用。
// err.to_string() 将错误转换为人类可读的字符串描述。
CoreError::Watch(err.to_string())
}
}
// ─── 单元测试 ────────────────────────────────────────────────────────────────
// `#[cfg(test)]` — 条件编译属性,以下代码只在 cargo test 时编译。
#[cfg(test)]
mod tests {
// `use super::*;` — 引入父模块error.rs的所有公开内容。
use super::*;
#[test]
// 测试Io 错误的 Display 输出应包含 context 和 source 的信息。
fn test_io_error_display() {
// 创建一个 Io 错误实例:
// - source: 一个 "file missing" 的 NotFound 错误。
// - context: "reading config"。
//
// std::io::Error::new() 创建一个新的 I/O 错误。
// - ErrorKind::NotFound 是错误类别(文件未找到)。
// - "file missing" 是错误消息。
let err = CoreError::Io {
source: std::io::Error::new(std::io::ErrorKind::NotFound, "file missing"),
// `.into()` 将 &str 转换为 String。
context: "reading config".into(),
};
// .to_string() 会使用 #[error("...")] 模板生成字符串。
let msg = err.to_string();
// 验证输出包含上下文描述。
assert!(
msg.contains("reading config"),
"display should contain context"
);
// 验证输出包含底层错误消息。
assert!(
msg.contains("file missing"),
"display should contain source message"
@@ -95,7 +246,11 @@ mod tests {
}
#[test]
// 测试Parse 错误的 Display 输出应包含行号。
fn test_parse_error_display() {
// serde_json::from_str::<serde_json::Value>("{bad}") 尝试解析无效 JSON。
// ::<serde_json::Value> 是 turbofish 语法,指定解析目标类型。
// .unwrap_err() 断言解析失败,并返回错误值(与 unwrap() 相反)。
let json_err: serde_json::Error =
serde_json::from_str::<serde_json::Value>("{bad}").unwrap_err();
let err = CoreError::Parse {
@@ -103,10 +258,12 @@ mod tests {
line: 42,
};
let msg = err.to_string();
// 验证输出包含行号 "42"。
assert!(msg.contains("42"), "display should contain line number");
}
#[test]
// 测试Search 错误的 Display 输出应包含 pattern 和 reason。
fn test_search_error_display() {
let err = CoreError::Search {
pattern: "[invalid".into(),
@@ -121,40 +278,59 @@ mod tests {
}
#[test]
// 测试From<std::io::Error> 实现能正确转换,且默认上下文为 "I/O operation"。
fn test_from_io_error() {
// 创建一个 PermissionDenied 类型的 I/O 错误。
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
// .into() 利用 From trait 将 std::io::Error 转换为 CoreError。
let core_err: CoreError = io_err.into();
// match 模式匹配验证转换结果。
match core_err {
// `{ context, .. }` 中的 .. 表示忽略其他字段source
CoreError::Io { context, .. } => assert_eq!(context, "I/O operation"),
// 如果转换成了其他变体说明有问题panic。
other => panic!("expected Io variant, got {other:?}"),
}
}
#[test]
// 测试From<notify::Error> 实现能正确转换为 Watch 变体。
fn test_from_notify_error() {
// notify::Error::watch_not_found() 创建一个"监控未找到"的错误。
let notify_err = notify::Error::watch_not_found();
let core_err: CoreError = notify_err.into();
match core_err {
// 验证 Watch 变体中的消息不为空。
CoreError::Watch(msg) => assert!(!msg.is_empty()),
other => panic!("expected Watch variant, got {other:?}"),
}
}
#[test]
// 测试Result 类型别名能正确使用 Ok 和 Err。
fn test_result_type_alias() {
// Ok(42) — 成功值,类型为 Result<i32>(展开为 std::result::Result<i32, CoreError>)。
let ok: Result<i32> = Ok(42);
// Err(CoreError::Other(...)) — 错误值。
let err: Result<i32> = Err(CoreError::Other("something went wrong".into()));
// .is_ok() 检查是否为 Ok 变体。
assert!(ok.is_ok());
// .is_err() 检查是否为 Err 变体。
assert!(err.is_err());
}
#[test]
// 测试CoreError 实现了 std::error::Error trait 的 source() 方法,
// 能返回底层错误(错误链)。
fn test_error_source_chain() {
let io_err = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof");
let core_err = CoreError::Io {
source: io_err,
context: "test".into(),
};
// std::error::Error::source(&core_err) 调用 source() 方法获取底层错误。
// 这个方法是由 thiserror 的 #[source] 属性自动生成的。
// 返回 Option<&(dyn Error)>,即底层错误的引用(可能为 None
let source = std::error::Error::source(&core_err);
assert!(
source.is_some(),