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:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user