From 8e977af52c7fd6a022f297152354fe0c8b197626 Mon Sep 17 00:00:00 2001 From: dailz Date: Fri, 10 Apr 2026 21:17:57 +0800 Subject: [PATCH] feat(core): define core types with tests Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- crates/core/src/types.rs | 194 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 1 deletion(-) diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 159b696..f60c035 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -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 { + 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, + pub level: Option, + pub fields: HashMap, +} + +/// 搜索匹配结果 +#[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>, + pub time_range: Option<(String, String)>, +} + +/// 书签 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bookmark { + pub line_number: u64, + pub label: Option, + 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::(), Ok(LogLevel::Error)); + assert_eq!("error".parse::(), Ok(LogLevel::Error)); + assert_eq!("ERR".parse::(), Ok(LogLevel::Error)); + assert_eq!("SEVERE".parse::(), Ok(LogLevel::Error)); + assert_eq!("FATAL".parse::(), Ok(LogLevel::Error)); + } + + #[test] + fn test_from_str_warn_variants() { + assert_eq!("WARN".parse::(), Ok(LogLevel::Warn)); + assert_eq!("warn".parse::(), Ok(LogLevel::Warn)); + assert_eq!("WARNING".parse::(), Ok(LogLevel::Warn)); + assert_eq!("WRN".parse::(), Ok(LogLevel::Warn)); + } + + #[test] + fn test_from_str_info_variants() { + assert_eq!("INFO".parse::(), Ok(LogLevel::Info)); + assert_eq!("info".parse::(), Ok(LogLevel::Info)); + assert_eq!("INFORMATION".parse::(), Ok(LogLevel::Info)); + } + + #[test] + fn test_from_str_debug_variants() { + assert_eq!("DEBUG".parse::(), Ok(LogLevel::Debug)); + assert_eq!("debug".parse::(), Ok(LogLevel::Debug)); + assert_eq!("DBG".parse::(), Ok(LogLevel::Debug)); + } + + #[test] + fn test_from_str_trace_variants() { + assert_eq!("TRACE".parse::(), Ok(LogLevel::Trace)); + assert_eq!("trace".parse::(), Ok(LogLevel::Trace)); + assert_eq!("TRC".parse::(), Ok(LogLevel::Trace)); + } + + #[test] + fn test_from_str_unknown() { + assert_eq!( + "FOOBAR".parse::(), + 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); + } +}