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