From 9082726e478a908f05337e3540576f438283eafd Mon Sep 17 00:00:00 2001 From: dailz Date: Sat, 11 Apr 2026 16:26:24 +0800 Subject: [PATCH] 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 --- crates/core/src/error.rs | 186 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 5 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 751e172..1fb7bfc 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1,50 +1,177 @@ +// ─── error.rs ───────────────────────────────────────────────────────────────── +// 这个文件定义了整个 log-viewer-core 库使用的错误类型(CoreError)和一个 +// 便捷的 Result 类型别名。 +// +// 在 Rust 中,错误处理通常使用 Result 类型: +// - 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 }, + // 注意 {bytes:?} 中的 :? 是 Debug 格式化(而不是 Display)。 + // Debug 格式化会输出类似 [255, 254] 这样的数组表示, + // 而 Display 格式化对于 Vec 没有默认实现,所以这里用 :?。 + Encoding { + // `line: u64` — 编码错误发生的行号。 + line: u64, + // `bytes: Vec` — 导致编码错误的前若干字节的原始内容。 + // 用于调试,帮助用户了解文件中哪些字节不是有效的 UTF-8。 + bytes: Vec, + }, + // ─── 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 = std::result::Result;` 定义了一个类型别名。 +// +// type 关键字用于创建类型别名(type alias),即给一个已有的类型起一个新名字。 +// 类似于 C 的 typedef 或 Go 的 type alias。 +// +// 这样做的目的是简化代码——项目中的函数不需要每次都写: +// fn open(path: &Path) -> std::result::Result +// 只需要写: +// fn open(path: &Path) -> Result +// +// 因为错误类型已经固定为 CoreError,调用者只需关心成功时的返回值类型 T。 +// 这是 Rust 项目中非常常见的模式(标准库本身也大量使用,如 io::Result)。 +// +// 是泛型参数(generic parameter),表示 T 可以是任何类型。 +// 例如 Result 展开为 std::result::Result, +// Result 展开为 std::result::Result。 pub type Result = std::result::Result; +// ─── From 实现:自动类型转换 ──────────────────────────────────────────────── +// `impl From for B` 实现了 From trait,表示"可以从类型 A 转换为类型 B"。 +// 这个 trait 只需要实现一个方法:fn from(value: A) -> Self。 +// +// 实现 From for B 的好处: +// 1. 可以使用 B::from(a_value) 显式转换。 +// 2. 更重要的是,Rust 会自动实现反方向的 Into trait, +// 即 A 类型可以调用 .into() 方法转换为 B 类型。 +// 3. 在函数返回 Result 时,可以用 ? 操作符自动将 +// 底层错误(如 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 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。 CoreError::Io { source: err, context: "I/O operation".into(), @@ -52,42 +179,66 @@ impl From for CoreError { } } +// ─── 从 serde_json::Error 转换为 CoreError ────────────────────────────────── +// 当 JSON 解析失败时,自动将 serde_json::Error 包装为 CoreError::Parse。 impl From 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 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 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::("{bad}") 尝试解析无效 JSON。 + // :: 是 turbofish 语法,指定解析目标类型。 + // .unwrap_err() 断言解析失败,并返回错误值(与 unwrap() 相反)。 let json_err: serde_json::Error = serde_json::from_str::("{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 实现能正确转换,且默认上下文为 "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 实现能正确转换为 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(展开为 std::result::Result)。 let ok: Result = Ok(42); + // Err(CoreError::Other(...)) — 错误值。 let err: Result = 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(),