#[derive(Debug, Clone)]
pub struct Config {
pub threshold: u8,
pub min_collapse: usize,
pub normalize_timestamps: bool,
pub normalize_hashes: bool,
pub normalize_ports: bool,
pub normalize_ips: bool,
pub normalize_fqdns: bool,
pub normalize_uuids: bool,
pub normalize_pids: bool,
pub normalize_emails: bool,
pub normalize_paths: bool,
pub normalize_json: bool,
pub normalize_durations: bool,
pub normalize_kubernetes: bool,
pub normalize_http_status: bool,
pub normalize_brackets: bool,
pub normalize_key_value: bool,
pub normalize_quoted: bool,
pub normalize_names: bool,
pub output_format: String,
pub stats: bool,
pub preserve_color: bool,
pub compact: bool,
pub preflight: bool,
pub summary: bool,
pub essence_mode: bool, pub thread_count: Option<usize>,
pub max_line_length: Option<usize>, pub max_lines: Option<usize>, pub sanitize_pii: bool, pub top_n: Option<usize>, pub stats_json: bool, pub fail_pattern: Option<String>, }
impl Default for Config {
fn default() -> Self {
Self {
threshold: 85,
min_collapse: 3,
normalize_timestamps: true,
normalize_hashes: true,
normalize_ports: true,
normalize_ips: true,
normalize_fqdns: true,
normalize_uuids: true,
normalize_pids: true,
normalize_emails: true,
normalize_paths: true,
normalize_json: true,
normalize_durations: true,
normalize_kubernetes: true,
normalize_http_status: true,
normalize_brackets: true,
normalize_key_value: true,
normalize_quoted: true,
normalize_names: true,
output_format: "text".to_string(),
stats: true,
preserve_color: false,
compact: true,
preflight: false,
summary: false,
essence_mode: false, thread_count: None,
max_line_length: Some(1024 * 1024), max_lines: None, sanitize_pii: false, top_n: None, stats_json: false, fail_pattern: None, }
}
}
pub fn parse_size_suffix(input: &str) -> Result<usize, String> {
let input = input.trim();
if let Some(num_str) = input.strip_suffix('K').or_else(|| input.strip_suffix('k')) {
num_str
.parse::<usize>()
.map(|n| n * 1024)
.map_err(|_| format!("Invalid number before 'K': {num_str}"))
} else if let Some(num_str) = input.strip_suffix('M').or_else(|| input.strip_suffix('m')) {
num_str
.parse::<usize>()
.map(|n| n * 1024 * 1024)
.map_err(|_| format!("Invalid number before 'M': {num_str}"))
} else if let Some(num_str) = input.strip_suffix('G').or_else(|| input.strip_suffix('g')) {
num_str
.parse::<usize>()
.map(|n| n * 1024 * 1024 * 1024)
.map_err(|_| format!("Invalid number before 'G': {num_str}"))
} else {
input
.parse::<usize>()
.map_err(|_| format!("Invalid number: {input}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_thread_count_is_none() {
let config = Config::default();
assert!(
config.thread_count.is_none(),
"Default thread_count should be None (auto-detect)"
);
}
#[test]
fn test_single_threaded_mode_detection() {
let config = Config {
thread_count: Some(1),
..Default::default()
};
assert_eq!(
config.thread_count,
Some(1),
"Single-threaded mode should be detected via thread_count == Some(1)"
);
}
#[test]
fn test_auto_detect_mode_detection() {
let config = Config::default();
assert!(
config.thread_count.is_none(),
"Auto-detect mode when thread_count is None"
);
}
#[test]
fn test_multi_thread_mode_detection() {
let config = Config {
thread_count: Some(4),
..Default::default()
};
assert_eq!(
config.thread_count,
Some(4),
"Multi-threaded mode with explicit count"
);
}
#[test]
fn test_thread_count_validation() {
let config = Config {
thread_count: Some(0),
..Default::default()
};
assert_eq!(config.thread_count, Some(0));
}
#[test]
fn parse_size_plain_number() {
assert_eq!(parse_size_suffix("1024").unwrap(), 1024);
}
#[test]
fn parse_size_k_suffix() {
assert_eq!(parse_size_suffix("4K").unwrap(), 4 * 1024);
assert_eq!(parse_size_suffix("4k").unwrap(), 4 * 1024);
}
#[test]
fn parse_size_m_suffix() {
assert_eq!(parse_size_suffix("2M").unwrap(), 2 * 1024 * 1024);
assert_eq!(parse_size_suffix("2m").unwrap(), 2 * 1024 * 1024);
}
#[test]
fn parse_size_g_suffix() {
assert_eq!(parse_size_suffix("1G").unwrap(), 1024 * 1024 * 1024);
assert_eq!(parse_size_suffix("1g").unwrap(), 1024 * 1024 * 1024);
}
#[test]
fn parse_size_invalid() {
assert!(parse_size_suffix("abc").is_err());
assert!(parse_size_suffix("K").is_err()); assert!(parse_size_suffix("M").is_err());
assert!(parse_size_suffix("G").is_err());
}
#[test]
fn parse_size_whitespace() {
assert_eq!(parse_size_suffix(" 1024 ").unwrap(), 1024);
}
#[test]
fn default_config_values() {
let c = Config::default();
assert_eq!(c.threshold, 85);
assert_eq!(c.min_collapse, 3);
assert!(c.normalize_timestamps);
assert!(c.normalize_hashes);
assert!(c.normalize_ports);
assert!(c.normalize_ips);
assert!(c.normalize_fqdns);
assert!(c.normalize_uuids);
assert!(c.normalize_pids);
assert!(c.normalize_emails);
assert!(c.normalize_paths);
assert!(c.normalize_json);
assert!(c.normalize_durations);
assert!(c.normalize_kubernetes);
assert!(c.normalize_http_status);
assert!(c.normalize_brackets);
assert!(c.normalize_key_value);
assert!(c.normalize_quoted);
assert!(c.normalize_names);
assert_eq!(c.output_format, "text");
assert!(c.stats);
assert!(!c.preserve_color);
assert!(c.compact);
assert!(!c.preflight);
assert!(!c.summary);
assert!(!c.essence_mode);
assert_eq!(c.max_line_length, Some(1024 * 1024));
assert!(c.max_lines.is_none());
assert!(!c.sanitize_pii);
assert!(c.top_n.is_none());
assert!(!c.stats_json);
assert!(c.fail_pattern.is_none());
}
}