docs(core): add Chinese comments to types module

Add detailed Chinese comments explaining Rust derive macros, FromStr/Display traits, serde serialization, and core data types.

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:30:20 +08:00
parent 9082726e47
commit 67a118a8c8

View File

@@ -1,112 +1,252 @@
// ─── types.rs ─────────────────────────────────────────────────────────────────
// 这个文件定义了 log-viewer-core 库中使用的核心数据类型(数据模型)。
// 包括日志级别LogLevel、日志条目LogEntry、搜索结果SearchResult
// 搜索查询SearchQuery、搜索过滤器SearchFilter、书签Bookmark
// 文件会话FileSession等。
//
// 这些类型是整个应用的"通用语言"各个模块io、parser、tui 等)都依赖它们
// 来传递和处理数据。
// ──────────────────────────────────────────────────────────────────────────────
// HashMap — 哈希表(字典)类型,用于存储键值对。
// 这里用于存储日志条目中除 timestamp/level 之外的其他 JSON 字段。
use std::collections::HashMap;
// fmt — 格式化模块,提供 Display trait自定义 .to_string() 行为)。
use std::fmt;
// FromStr — 字符串解析 trait允许从字符串解析为自定义类型如 "INFO" → LogLevel::Info
use std::str::FromStr;
// serde 是 Rust 中最流行的序列化/反序列化框架。
// - Serialize: 将 Rust 数据结构转换为其他格式(如 JSON、TOML
// - Deserialize: 将其他格式(如 JSON、TOML转换回 Rust 数据结构。
// 这样 LogLevel、LogEntry 等类型就可以被序列化为 JSON 保存,或从 JSON 反序列化恢复。
use serde::{Deserialize, Serialize};
// serde_json::Value — 可以表示任意 JSON 值的枚举类型。
use serde_json::Value;
// ─── LogLevel 枚举 ──────────────────────────────────────────────────────────
/// 日志级别
///
/// 表示一条日志的严重程度。不同日志系统使用不同的级别名称,
/// 这里统一定义为 5 个标准级别 + 1 个"未知"级别。
///
/// # 派生属性说明
/// - `Debug`: 允许用 `{:?}` 格式化打印,用于调试。
/// - `Clone`: 允许克隆(深拷贝)该值。
/// - `PartialEq`: 允许用 `==` 和 `!=` 比较两个值是否相等。
/// - `Eq`: 表示"完全相等"关系PartialEq 的增强版,要求满足等价关系的数学性质)。
/// - `Hash`: 允许将该值用作 HashMap 的键(需要 Eq + Hash 配合)。
/// - `Serialize`: 允许序列化为 JSON 等格式(如 `"Info"` → JSON 字符串 `"Info"`)。
/// - `Deserialize`: 允许从 JSON 等格式反序列化(如 JSON 字符串 `"Info"` → `LogLevel::Info`)。
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
// 每个变体代表一种日志级别,从严重到轻微排列:
Error, // 错误:程序出现了问题,需要关注。
Warn, // 警告:潜在的问题,不一定需要立即处理。
Info, // 信息:一般的运行信息(最常用的级别)。
Debug, // 调试:用于开发调试的详细信息。
Trace, // 跟踪:非常详细的调试信息(比 Debug 更细粒度)。
// Unknown 是一个"带数据的变体"tuple variant包含一个 String。
// 当日志级别不在上述 5 种之内时,用 Unknown 保存原始的级别字符串。
// 例如日志级别为 "CRITICAL" 时,会被解析为 Unknown("CRITICAL")。
Unknown(String),
}
// ─── FromStr 实现:从字符串解析为 LogLevel ──────────────────────────────────
// `impl FromStr for LogLevel` 为 LogLevel 实现 FromStr trait。
// 这使得可以使用 `"INFO".parse::<LogLevel>()` 或 `str::parse::<LogLevel>(s)`
// 将字符串解析为 LogLevel 枚举值。
//
// FromStr trait 要求定义两个东西:
// 1. type Err — 解析失败时的错误类型。
// 2. fn from_str(s: &str) -> Result<Self, Self::Err> — 解析逻辑。
impl FromStr for LogLevel {
// `type Err = std::convert::Infallible` — 错误类型设为 Infallible"不可失败的")。
// Infallible 是一个空枚举(没有变体),表示"这个解析永远不会失败"。
// 因为对于未知的日志级别,我们返回 Unknown(...) 而不是报错,
// 所以解析操作确实不可能失败。
type Err = std::convert::Infallible;
// `fn from_str(s: &str) -> Result<Self, Self::Err>` — 解析函数。
// 接收一个字符串切片 &str返回 Result<LogLevel, Infallible>。
// 由于 Err 类型是 Infallible实际上返回值总是 Ok(LogLevel)。
fn from_str(s: &str) -> Result<Self, Self::Err> {
// `s.to_uppercase()` — 将字符串转换为大写,实现不区分大小写的匹配。
// 例如 "info"、"Info"、"INFO" 都会被转换为 "INFO"。
// 返回一个新的 String堆分配
//
// `.as_str()` — 将 String 转换回 &str字符串切片引用
// 因为 match 需要匹配 &str 而不是 String。
match s.to_uppercase().as_str() {
// `|` 在 match 分支中表示"或"multiple patterns
// "ERROR" | "ERR" | "SEVERE" | "FATAL" 都匹配到 LogLevel::Error。
"ERROR" | "ERR" | "SEVERE" | "FATAL" => Ok(LogLevel::Error),
"WARN" | "WARNING" | "WRN" => Ok(LogLevel::Warn),
"INFO" | "INFORMATION" => Ok(LogLevel::Info),
"DEBUG" | "DBG" => Ok(LogLevel::Debug),
"TRACE" | "TRC" => Ok(LogLevel::Trace),
// `_` 是通配符,匹配所有未被上面分支捕获的值。
// 对于未知级别,包装为 Unknown 并保存原始字符串。
// s.to_string() 将 &str 转换为 String注意这里用原始的 s不是大写后的
_ => Ok(LogLevel::Unknown(s.to_string())),
}
}
}
// ─── Display 实现:自定义 LogLevel 的显示格式 ────────────────────────────────
// `impl fmt::Display for LogLevel` 实现 Display trait
// 定义了用 `{}` 格式化(即 .to_string())时的输出。
impl fmt::Display for LogLevel {
// `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result`
// - &self: 对自身的不可变引用。
// - f: &mut fmt::Formatter 是格式化输出"写入器",用于写入输出字符串。
// <'_> 是生命周期省略语法,表示 Formatter 借用了一个短生命周期的引用。
// - fmt::Result: 结果类型,成功返回 Ok(()),失败返回 Err极少见
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
// `write!(f, "ERROR")` — 向格式化写入器写入字符串 "ERROR"。
// write! 是一个宏(注意感叹号),类似于 println!,但写入到指定的 f 而不是标准输出。
LogLevel::Error => write!(f, "ERROR"),
LogLevel::Warn => write!(f, "WARN"),
LogLevel::Info => write!(f, "INFO"),
LogLevel::Debug => write!(f, "DEBUG"),
LogLevel::Trace => write!(f, "TRACE"),
// `{s}` 是格式化占位符,将 s 的值插入到输出字符串中。
// 例如 Unknown("custom") → "UNKNOWN(custom)"。
LogLevel::Unknown(s) => write!(f, "UNKNOWN({s})"),
}
}
}
// ─── LogEntry 结构体 ────────────────────────────────────────────────────────
/// 一行解析后的日志
///
/// 表示日志文件中经过解析器处理后的一行内容。
/// 包含原始文本、行号、时间戳、日志级别以及其他 JSON 字段。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LogEntry {
// `pub` 表示字段是公开的,外部代码可以直接访问。
// 如果不加 pub字段只能在定义该结构体的模块内访问。
/// 行号(从 0 开始)
pub line_number: u64,
/// 原始行内容(未经处理的完整文本)
pub raw_line: String,
/// 时间戳如果日志中有时间字段。Option<String> 表示时间戳可能不存在。
/// Some("2024-01-01T00:00:00Z") 表示有时间戳None 表示没有。
pub timestamp: Option<String>,
/// 日志级别如果日志中有级别字段。Option<LogLevel> 表示级别可能不存在。
pub level: Option<LogLevel>,
/// 其他 JSON 字段(除 timestamp 和 level 外的所有字段)。
/// HashMap<String, Value> 是一个字典,键是字段名,值是 JSON 值。
/// 例如 {"message": "hello", "request_id": "abc123"}。
pub fields: HashMap<String, Value>,
}
// ─── SearchResult 结构体 ────────────────────────────────────────────────────
/// 搜索匹配结果
///
/// 记录一次搜索匹配在文件中的位置信息:
/// 在哪一行的哪个字符范围内匹配到了搜索内容。
#[derive(Debug, Clone, PartialEq)]
pub struct SearchResult {
/// 匹配所在的行号
pub line_number: u64,
/// 匹配开始处的字符偏移量(从 0 开始)
pub match_start: usize,
/// 匹配结束处的字符偏移量(不包含,即左闭右开区间 [match_start, match_end)
pub match_end: usize,
}
// ─── SearchQuery 枚举 ──────────────────────────────────────────────────────
/// 搜索查询
///
/// 表示用户输入的搜索内容,支持两种模式:
/// - Regex: 正则表达式搜索(模式匹配)
/// - Keyword: 关键词搜索(精确文本查找)
#[derive(Debug, Clone, PartialEq)]
pub enum SearchQuery {
// Regex(String) — "元组变体"tuple variant包含一个正则表达式字符串。
// 例如 SearchQuery::Regex("error|warn")。
Regex(String),
// Keyword(String) — 包含一个关键词字符串。
// 例如 SearchQuery::Keyword("connection timeout")。
Keyword(String),
}
// ─── SearchFilter 结构体 ────────────────────────────────────────────────────
/// 搜索过滤器
///
/// 用于在搜索基础上进一步过滤结果,可以按日志级别和/或时间范围筛选。
#[derive(Debug, Clone, PartialEq)]
pub struct SearchFilter {
/// 按日志级别过滤。None 表示不过滤级别Some(vec![...]) 表示只保留这些级别。
/// Option<Vec<LogLevel>> 意味着这个过滤器字段是可选的。
pub levels: Option<Vec<LogLevel>>,
/// 按时间范围过滤。None 表示不过滤时间Some(("起始时间", "结束时间")) 表示只保留此范围内的日志。
/// (String, String) 是一个元组tuple包含起始和结束时间字符串。
pub time_range: Option<(String, String)>,
}
// ─── Bookmark 结构体 ────────────────────────────────────────────────────────
/// 书签
///
/// 用户可以在日志中标记某一行作为书签,方便后续快速跳转。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Bookmark {
/// 被标记的行号
pub line_number: u64,
/// 书签标签用户自定义的名称。None 表示没有标签。
pub label: Option<String>,
/// 书签创建时间(字符串形式的时间戳,如 "2024-01-01T12:00:00"
pub created_at: String,
}
// ─── FileSession 结构体 ─────────────────────────────────────────────────────
/// 打开的文件会话信息
///
/// 表示当前打开的日志文件的基本信息(元数据),
/// 用于在 UI 中显示文件名、大小、行数等信息。
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FileSession {
/// 文件路径
pub file_path: std::path::PathBuf,
/// 用于在 UI 中显示的文件名(通常是文件名部分,不包含目录)
pub display_name: String,
/// 文件总行数
pub total_lines: u64,
/// 文件大小(字节)
pub file_size: u64,
}
// ─── 单元测试 ────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
// 测试ERROR 级别的各种写法都能正确解析。
// .parse::<LogLevel>() 利用 FromStr trait 将字符串解析为 LogLevel。
// ::<LogLevel> 是 turbofish 语法,指定泛型参数。
fn test_from_str_error_variants() {
// 大写 "ERROR"
assert_eq!("ERROR".parse::<LogLevel>(), Ok(LogLevel::Error));
// 小写 "error" — 不区分大小写
assert_eq!("error".parse::<LogLevel>(), Ok(LogLevel::Error));
// 缩写 "ERR"
assert_eq!("ERR".parse::<LogLevel>(), Ok(LogLevel::Error));
// "SEVERE" — Java 日志中常用的严重级别
assert_eq!("SEVERE".parse::<LogLevel>(), Ok(LogLevel::Error));
// "FATAL" — 表示致命错误
assert_eq!("FATAL".parse::<LogLevel>(), Ok(LogLevel::Error));
}
#[test]
// 测试WARN 级别的各种写法。
fn test_from_str_warn_variants() {
assert_eq!("WARN".parse::<LogLevel>(), Ok(LogLevel::Warn));
assert_eq!("warn".parse::<LogLevel>(), Ok(LogLevel::Warn));
@@ -115,6 +255,7 @@ mod tests {
}
#[test]
// 测试INFO 级别的各种写法。
fn test_from_str_info_variants() {
assert_eq!("INFO".parse::<LogLevel>(), Ok(LogLevel::Info));
assert_eq!("info".parse::<LogLevel>(), Ok(LogLevel::Info));
@@ -122,6 +263,7 @@ mod tests {
}
#[test]
// 测试DEBUG 级别的各种写法。
fn test_from_str_debug_variants() {
assert_eq!("DEBUG".parse::<LogLevel>(), Ok(LogLevel::Debug));
assert_eq!("debug".parse::<LogLevel>(), Ok(LogLevel::Debug));
@@ -129,6 +271,7 @@ mod tests {
}
#[test]
// 测试TRACE 级别的各种写法。
fn test_from_str_trace_variants() {
assert_eq!("TRACE".parse::<LogLevel>(), Ok(LogLevel::Trace));
assert_eq!("trace".parse::<LogLevel>(), Ok(LogLevel::Trace));
@@ -136,20 +279,25 @@ mod tests {
}
#[test]
// 测试:无法识别的级别字符串应该解析为 Unknown 变体。
fn test_from_str_unknown() {
assert_eq!(
"FOOBAR".parse::<LogLevel>(),
// .into() 在这里将 &str 转换为 String。
Ok(LogLevel::Unknown("FOOBAR".into()))
);
}
#[test]
// 测试LogLevel 的 Display 输出格式是否正确。
fn test_display_output() {
assert_eq!(LogLevel::Error.to_string(), "ERROR");
assert_eq!(LogLevel::Warn.to_string(), "WARN");
assert_eq!(LogLevel::Info.to_string(), "INFO");
assert_eq!(LogLevel::Debug.to_string(), "DEBUG");
assert_eq!(LogLevel::Trace.to_string(), "TRACE");
// Unknown 变体的显示格式是 "UNKNOWN(原始字符串)"。
// .into() 将 &str 转换为 String。
assert_eq!(
LogLevel::Unknown("custom".into()).to_string(),
"UNKNOWN(custom)"
@@ -157,10 +305,14 @@ mod tests {
}
#[test]
// 测试LogEntry 结构体的构造和字段访问。
fn test_log_entry_construction() {
// 创建一个 HashMap 并插入一个键值对。
let mut fields = HashMap::new();
// .insert() 向 HashMap 中插入键值对。
fields.insert("key".to_string(), Value::String("value".to_string()));
// 使用结构体字面量语法创建 LogEntry 实例。
let entry = LogEntry {
line_number: 42,
raw_line: "test line".to_string(),
@@ -169,10 +321,12 @@ mod tests {
fields,
};
// 逐字段验证。
assert_eq!(entry.line_number, 42);
assert_eq!(entry.raw_line, "test line");
assert_eq!(entry.timestamp, Some("2024-01-01T00:00:00".to_string()));
assert_eq!(entry.level, Some(LogLevel::Info));
// fields.get("key") 返回 Option<&Value>(值的引用)。
assert_eq!(
entry.fields.get("key"),
Some(&Value::String("value".to_string()))
@@ -180,6 +334,7 @@ mod tests {
}
#[test]
// 测试SearchResult 结构体的构造和字段访问。
fn test_search_result_construction() {
let result = SearchResult {
line_number: 10,