Skip to main content

otelite_storage/
config.rs

1//! Storage configuration
2
3use otelite_core::storage::{Result, StorageError};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7/// Storage configuration
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct StorageConfig {
10    /// Data directory path
11    pub data_dir: PathBuf,
12
13    /// Retention period in days (1-365)
14    pub retention_days: u32,
15
16    /// Purge schedule (cron-like format)
17    pub purge_schedule: String,
18
19    /// Enable automatic purging
20    pub auto_purge_enabled: bool,
21
22    /// Batch size for purge operations
23    pub purge_batch_size: usize,
24}
25
26impl Default for StorageConfig {
27    fn default() -> Self {
28        Self {
29            data_dir: Self::default_data_dir(),
30            retention_days: 90,
31            purge_schedule: "0 2 * * *".to_string(), // Daily at 2 AM
32            auto_purge_enabled: true,
33            purge_batch_size: 1000,
34        }
35    }
36}
37
38impl StorageConfig {
39    pub fn default_data_dir() -> PathBuf {
40        dirs::home_dir()
41            .unwrap_or_else(|| PathBuf::from("."))
42            .join(".otelite")
43            .join("data")
44    }
45
46    /// Create configuration from environment variables
47    pub fn from_env() -> Result<Self> {
48        let mut config = Self::default();
49
50        if let Ok(data_dir) = std::env::var("OTELITE_DATA_DIR") {
51            config.data_dir = PathBuf::from(data_dir);
52        }
53
54        if let Ok(retention_days) = std::env::var("OTELITE_RETENTION_DAYS") {
55            config.retention_days = retention_days
56                .parse()
57                .map_err(|e| StorageError::ConfigError(format!("Invalid retention_days: {}", e)))?;
58        }
59
60        if let Ok(purge_schedule) = std::env::var("OTELITE_PURGE_SCHEDULE") {
61            config.purge_schedule = purge_schedule;
62        }
63
64        if let Ok(auto_purge) = std::env::var("OTELITE_AUTO_PURGE_ENABLED") {
65            config.auto_purge_enabled = auto_purge.parse().unwrap_or(true);
66        }
67
68        config.validate()?;
69        Ok(config)
70    }
71
72    /// Validate configuration
73    pub fn validate(&self) -> Result<()> {
74        if self.retention_days < 1 || self.retention_days > 365 {
75            return Err(StorageError::ConfigError(
76                "Retention days must be between 1 and 365".to_string(),
77            ));
78        }
79
80        if self.purge_batch_size == 0 {
81            return Err(StorageError::ConfigError(
82                "Purge batch size must be greater than 0".to_string(),
83            ));
84        }
85
86        Ok(())
87    }
88
89    /// Builder method to set data directory
90    pub fn with_data_dir(mut self, data_dir: PathBuf) -> Self {
91        self.data_dir = data_dir;
92        self
93    }
94
95    /// Builder method to set retention days
96    pub fn with_retention_days(mut self, days: u32) -> Self {
97        self.retention_days = days;
98        self
99    }
100
101    /// Builder method to set purge schedule
102    pub fn with_purge_schedule(mut self, schedule: String) -> Self {
103        self.purge_schedule = schedule;
104        self
105    }
106
107    /// Builder method to enable/disable auto purge
108    pub fn with_auto_purge(mut self, enabled: bool) -> Self {
109        self.auto_purge_enabled = enabled;
110        self
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_default_config() {
120        let config = StorageConfig::default();
121        assert_eq!(config.retention_days, 90);
122        assert_eq!(config.purge_schedule, "0 2 * * *");
123        assert!(config.auto_purge_enabled);
124        assert_eq!(config.purge_batch_size, 1000);
125    }
126
127    #[test]
128    fn test_default_data_dir_is_dotfile() {
129        // Must stay under ~/.otelite/data — not ~/Library or ~/.local/share.
130        let dir = StorageConfig::default_data_dir();
131        let home = dirs::home_dir().expect("home dir must exist in test env");
132        assert!(
133            dir.starts_with(&home),
134            "data dir {:?} should be under home {:?}",
135            dir,
136            home
137        );
138        let rel = dir.strip_prefix(&home).unwrap();
139        assert_eq!(
140            rel,
141            std::path::Path::new(".otelite/data"),
142            "data dir must be ~/.otelite/data, got {:?}",
143            dir
144        );
145    }
146
147    #[test]
148    fn test_config_validation() {
149        let mut config = StorageConfig::default();
150        assert!(config.validate().is_ok());
151
152        config.retention_days = 0;
153        assert!(config.validate().is_err());
154
155        config.retention_days = 366;
156        assert!(config.validate().is_err());
157
158        config.retention_days = 90;
159        config.purge_batch_size = 0;
160        assert!(config.validate().is_err());
161    }
162
163    #[test]
164    fn test_config_builder() {
165        let config = StorageConfig::default()
166            .with_retention_days(30)
167            .with_auto_purge(false);
168
169        assert_eq!(config.retention_days, 30);
170        assert!(!config.auto_purge_enabled);
171    }
172}