use std::env;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DalLevel {
#[default]
Baseline,
Enhanced,
Maximum,
}
impl DalLevel {
pub fn from_env() -> Self {
let val = env::var("SNIPER_DAL_LEVEL").unwrap_or_default();
match val.trim().to_uppercase().as_str() {
"ENHANCED" => Self::Enhanced,
"MAXIMUM" => Self::Maximum,
_ => Self::Baseline,
}
}
}
#[derive(Debug, Clone)]
pub struct SniperConfig {
pub lock_timeout: Duration,
pub max_file_size: u64,
pub backup_retention_count: usize,
pub backup_max_age_days: u64,
pub audit_enabled: bool,
pub dal_level: DalLevel,
pub pid_base_ms: u64,
pub pid_entropy_scale: f64,
pub pid_pressure_scale: f64,
}
impl Default for SniperConfig {
fn default() -> Self {
Self {
lock_timeout: Duration::from_secs(30),
max_file_size: 100 * 1024 * 1024, backup_retention_count: 50,
backup_max_age_days: 30,
audit_enabled: true,
dal_level: DalLevel::default(),
pid_base_ms: 0,
pid_entropy_scale: 0.5,
pid_pressure_scale: 1.0,
}
}
}
impl SniperConfig {
pub fn from_env() -> Self {
let mut config = Self::default();
if let Ok(val) = env::var("SNIPER_LOCK_TIMEOUT") {
if let Ok(secs) = val.parse::<u64>() {
config.lock_timeout = Duration::from_secs(secs.max(1)); }
}
if let Ok(val) = env::var("SNIPER_MAX_FILE_SIZE") {
config.max_file_size = parse_size(&val).unwrap_or(config.max_file_size);
}
if let Ok(val) = env::var("SNIPER_BACKUP_RETENTION_COUNT") {
if let Ok(count) = val.parse::<usize>() {
config.backup_retention_count = count;
}
}
if let Ok(val) = env::var("SNIPER_BACKUP_MAX_AGE_DAYS") {
if let Ok(days) = val.parse::<u64>() {
config.backup_max_age_days = days;
}
}
if env::var("SNIPER_DISABLE_AUDIT").is_ok() {
config.audit_enabled = false;
}
config.dal_level = DalLevel::from_env();
if let Ok(val) = env::var("SNIPER_PID_BASE_MS") {
if let Ok(ms) = val.parse::<u64>() {
config.pid_base_ms = ms;
}
}
if let Ok(val) = env::var("SNIPER_PID_ENTROPY_SCALE") {
if let Ok(scale) = val.parse::<f64>() {
if (0.0..=100.0).contains(&scale) && !scale.is_nan() {
config.pid_entropy_scale = scale;
}
}
}
if let Ok(val) = env::var("SNIPER_PID_PRESSURE_SCALE") {
if let Ok(scale) = val.parse::<f64>() {
if (0.0..=100.0).contains(&scale) && !scale.is_nan() {
config.pid_pressure_scale = scale;
}
}
}
config
}
#[allow(clippy::cast_possible_truncation)] pub fn lock_timeout_ms(&self) -> u64 {
self.lock_timeout.as_millis() as u64
}
}
fn parse_size(s: &str) -> Option<u64> {
let s = s.trim().to_uppercase();
if s.ends_with("GB") {
let num = s[..s.len() - 2].trim().parse::<u64>().ok()?;
Some(num * 1024 * 1024 * 1024)
} else if s.ends_with("MB") {
let num = s[..s.len() - 2].trim().parse::<u64>().ok()?;
Some(num * 1024 * 1024)
} else if s.ends_with("KB") {
let num = s[..s.len() - 2].trim().parse::<u64>().ok()?;
Some(num * 1024)
} else if s.ends_with("B") {
s[..s.len() - 1].trim().parse::<u64>().ok()
} else {
s.parse::<u64>().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = SniperConfig::default();
assert_eq!(config.lock_timeout, Duration::from_secs(30));
assert_eq!(config.max_file_size, 100 * 1024 * 1024);
assert_eq!(config.backup_retention_count, 50);
assert_eq!(config.backup_max_age_days, 30);
assert!(config.audit_enabled);
}
#[test]
fn test_parse_size_bytes() {
assert_eq!(parse_size("100"), Some(100));
assert_eq!(parse_size("100B"), Some(100));
}
#[test]
fn test_parse_size_kb() {
assert_eq!(parse_size("10KB"), Some(10 * 1024));
assert_eq!(parse_size("10kb"), Some(10 * 1024));
}
#[test]
fn test_parse_size_mb() {
assert_eq!(parse_size("100MB"), Some(100 * 1024 * 1024));
assert_eq!(parse_size("1MB"), Some(1024 * 1024));
}
#[test]
fn test_parse_size_gb() {
assert_eq!(parse_size("1GB"), Some(1024 * 1024 * 1024));
assert_eq!(parse_size("2GB"), Some(2 * 1024 * 1024 * 1024));
}
#[test]
fn test_parse_size_whitespace() {
assert_eq!(parse_size(" 100 MB "), Some(100 * 1024 * 1024));
}
#[test]
fn test_parse_size_invalid() {
assert_eq!(parse_size("invalid"), None);
assert_eq!(parse_size("MB"), None);
assert_eq!(parse_size(""), None);
}
#[test]
fn test_config_from_env() {
let _config = SniperConfig::from_env();
}
#[test]
fn test_dal_level_from_env_baseline() {
std::env::remove_var("SNIPER_DAL_LEVEL");
assert_eq!(DalLevel::from_env(), DalLevel::Baseline);
}
#[test]
fn test_dal_level_from_env_enhanced() {
std::env::set_var("SNIPER_DAL_LEVEL", "Enhanced");
assert_eq!(DalLevel::from_env(), DalLevel::Enhanced);
std::env::remove_var("SNIPER_DAL_LEVEL");
}
#[test]
fn test_dal_level_from_env_maximum() {
std::env::set_var("SNIPER_DAL_LEVEL", "MAXIMUM");
assert_eq!(DalLevel::from_env(), DalLevel::Maximum);
std::env::remove_var("SNIPER_DAL_LEVEL");
}
#[test]
fn test_dal_level_from_env_case_insensitive() {
std::env::set_var("SNIPER_DAL_LEVEL", "maximum");
assert_eq!(DalLevel::from_env(), DalLevel::Maximum);
std::env::remove_var("SNIPER_DAL_LEVEL");
}
#[test]
fn test_dal_level_from_env_invalid_defaults_to_baseline() {
std::env::set_var("SNIPER_DAL_LEVEL", "garbage");
assert_eq!(DalLevel::from_env(), DalLevel::Baseline);
std::env::remove_var("SNIPER_DAL_LEVEL");
}
#[test]
fn test_config_from_env_overrides() {
let old_timeout = std::env::var("SNIPER_LOCK_TIMEOUT").ok();
let old_retention = std::env::var("SNIPER_BACKUP_RETENTION_COUNT").ok();
let old_audit = std::env::var("SNIPER_DISABLE_AUDIT").ok();
std::env::set_var("SNIPER_LOCK_TIMEOUT", "60");
std::env::set_var("SNIPER_BACKUP_RETENTION_COUNT", "10");
std::env::set_var("SNIPER_DISABLE_AUDIT", "1");
let config = SniperConfig::from_env();
assert_eq!(config.lock_timeout, Duration::from_secs(60));
assert_eq!(config.backup_retention_count, 10);
assert!(!config.audit_enabled);
fn restore(key: &str, val: Option<String>) {
match val {
Some(v) => std::env::set_var(key, v),
None => std::env::remove_var(key),
}
}
restore("SNIPER_LOCK_TIMEOUT", old_timeout);
restore("SNIPER_BACKUP_RETENTION_COUNT", old_retention);
restore("SNIPER_DISABLE_AUDIT", old_audit);
}
#[test]
fn test_config_from_env_pid_scales() {
std::env::set_var("SNIPER_PID_ENTROPY_SCALE", "2.5");
std::env::set_var("SNIPER_PID_PRESSURE_SCALE", "3.0");
let config = SniperConfig::from_env();
assert_eq!(config.pid_entropy_scale, 2.5);
assert_eq!(config.pid_pressure_scale, 3.0);
std::env::remove_var("SNIPER_PID_ENTROPY_SCALE");
std::env::remove_var("SNIPER_PID_PRESSURE_SCALE");
}
}