feat(core): define core types with tests

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-10 21:17:57 +08:00
parent 0ee82cbccd
commit 8e977af52c

View File

@@ -1 +1,193 @@
// Task 2 will implement types use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// 日志级别
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
Unknown(String),
}
impl FromStr for LogLevel {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"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),
_ => Ok(LogLevel::Unknown(s.to_string())),
}
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
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"),
LogLevel::Unknown(s) => write!(f, "UNKNOWN({s})"),
}
}
}
/// 一行解析后的日志
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LogEntry {
pub line_number: u64,
pub raw_line: String,
pub timestamp: Option<String>,
pub level: Option<LogLevel>,
pub fields: HashMap<String, Value>,
}
/// 搜索匹配结果
#[derive(Debug, Clone, PartialEq)]
pub struct SearchResult {
pub line_number: u64,
pub match_start: usize,
pub match_end: usize,
}
/// 搜索查询
#[derive(Debug, Clone, PartialEq)]
pub enum SearchQuery {
Regex(String),
Keyword(String),
}
/// 搜索过滤器
#[derive(Debug, Clone, PartialEq)]
pub struct SearchFilter {
pub levels: Option<Vec<LogLevel>>,
pub time_range: Option<(String, String)>,
}
/// 书签
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Bookmark {
pub line_number: u64,
pub label: Option<String>,
pub created_at: String,
}
/// 打开的文件会话信息
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FileSession {
pub file_path: std::path::PathBuf,
pub display_name: String,
pub total_lines: u64,
pub file_size: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_str_error_variants() {
assert_eq!("ERROR".parse::<LogLevel>(), Ok(LogLevel::Error));
assert_eq!("error".parse::<LogLevel>(), Ok(LogLevel::Error));
assert_eq!("ERR".parse::<LogLevel>(), Ok(LogLevel::Error));
assert_eq!("SEVERE".parse::<LogLevel>(), Ok(LogLevel::Error));
assert_eq!("FATAL".parse::<LogLevel>(), Ok(LogLevel::Error));
}
#[test]
fn test_from_str_warn_variants() {
assert_eq!("WARN".parse::<LogLevel>(), Ok(LogLevel::Warn));
assert_eq!("warn".parse::<LogLevel>(), Ok(LogLevel::Warn));
assert_eq!("WARNING".parse::<LogLevel>(), Ok(LogLevel::Warn));
assert_eq!("WRN".parse::<LogLevel>(), Ok(LogLevel::Warn));
}
#[test]
fn test_from_str_info_variants() {
assert_eq!("INFO".parse::<LogLevel>(), Ok(LogLevel::Info));
assert_eq!("info".parse::<LogLevel>(), Ok(LogLevel::Info));
assert_eq!("INFORMATION".parse::<LogLevel>(), Ok(LogLevel::Info));
}
#[test]
fn test_from_str_debug_variants() {
assert_eq!("DEBUG".parse::<LogLevel>(), Ok(LogLevel::Debug));
assert_eq!("debug".parse::<LogLevel>(), Ok(LogLevel::Debug));
assert_eq!("DBG".parse::<LogLevel>(), Ok(LogLevel::Debug));
}
#[test]
fn test_from_str_trace_variants() {
assert_eq!("TRACE".parse::<LogLevel>(), Ok(LogLevel::Trace));
assert_eq!("trace".parse::<LogLevel>(), Ok(LogLevel::Trace));
assert_eq!("TRC".parse::<LogLevel>(), Ok(LogLevel::Trace));
}
#[test]
fn test_from_str_unknown() {
assert_eq!(
"FOOBAR".parse::<LogLevel>(),
Ok(LogLevel::Unknown("FOOBAR".into()))
);
}
#[test]
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");
assert_eq!(
LogLevel::Unknown("custom".into()).to_string(),
"UNKNOWN(custom)"
);
}
#[test]
fn test_log_entry_construction() {
let mut fields = HashMap::new();
fields.insert("key".to_string(), Value::String("value".to_string()));
let entry = LogEntry {
line_number: 42,
raw_line: "test line".to_string(),
timestamp: Some("2024-01-01T00:00:00".to_string()),
level: Some(LogLevel::Info),
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));
assert_eq!(
entry.fields.get("key"),
Some(&Value::String("value".to_string()))
);
}
#[test]
fn test_search_result_construction() {
let result = SearchResult {
line_number: 10,
match_start: 5,
match_end: 9,
};
assert_eq!(result.line_number, 10);
assert_eq!(result.match_start, 5);
assert_eq!(result.match_end, 9);
}
}