Skip to main content

vidsage_core/config/
manager.rs

1//! Config manager implementations
2
3use super::{ConfigError, ConfigManager};
4use crate::{CoreError, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs::{self, File};
8use std::io::Write;
9use std::path::Path;
10use tokio::sync::RwLock;
11
12/// Configuration value enum
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum ConfigValue {
15    /// String value
16    String(String),
17
18    /// Integer value
19    Integer(i64),
20
21    /// Float value
22    Float(f64),
23
24    /// Boolean value
25    Boolean(bool),
26
27    /// Array value
28    Array(Vec<ConfigValue>),
29
30    /// Object value
31    Object(HashMap<String, ConfigValue>),
32
33    /// Null value
34    Null,
35}
36
37/// File-based configuration manager implementation
38pub struct ConfigManagerImpl {
39    /// Configuration data
40    data: RwLock<HashMap<String, serde_json::Value>>,
41
42    /// Default configuration values
43    defaults: HashMap<String, serde_json::Value>,
44}
45
46impl ConfigManagerImpl {
47    /// Create a new ConfigManagerImpl instance
48    pub fn new() -> Self {
49        Self {
50            data: RwLock::new(HashMap::new()),
51            defaults: HashMap::new(),
52        }
53    }
54
55    /// Create a new ConfigManagerImpl instance with default values
56    pub fn with_defaults(defaults: HashMap<String, serde_json::Value>) -> Self {
57        Self {
58            data: RwLock::new(defaults.clone()),
59            defaults,
60        }
61    }
62
63    /// Replace environment variables in a string
64    fn replace_env_vars(&self, input: &str) -> String {
65        let mut result = String::new();
66        let mut chars = input.chars().peekable();
67
68        while let Some(c) = chars.next() {
69            if c == '$' && chars.peek() == Some(&'{') {
70                // Skip the ${
71                chars.next();
72
73                // Extract the environment variable name
74                let mut var_name = String::new();
75                while let Some(&c) = chars.peek() {
76                    if c == '}' {
77                        chars.next();
78                        break;
79                    }
80                    var_name.push(chars.next().unwrap());
81                }
82
83                // Replace with the environment variable value if it exists
84                if let Ok(var_value) = std::env::var(&var_name) {
85                    result.push_str(&var_value);
86                } else {
87                    // Keep the original ${
88                    result.push_str(&format!("${{{}}}", var_name));
89                }
90            } else {
91                result.push(c);
92            }
93        }
94
95        result
96    }
97
98    /// Recursively replace environment variables in JSON values
99    fn replace_env_vars_in_json(&self, value: serde_json::Value) -> serde_json::Value {
100        match value {
101            serde_json::Value::String(s) => {
102                let replaced = self.replace_env_vars(&s);
103                serde_json::Value::String(replaced)
104            },
105            serde_json::Value::Array(arr) => {
106                let replaced = arr
107                    .into_iter()
108                    .map(|v| self.replace_env_vars_in_json(v))
109                    .collect();
110                serde_json::Value::Array(replaced)
111            },
112            serde_json::Value::Object(obj) => {
113                let replaced = obj
114                    .into_iter()
115                    .map(|(k, v)| (k, self.replace_env_vars_in_json(v)))
116                    .collect();
117                serde_json::Value::Object(replaced)
118            },
119            // Other types remain unchanged
120            v => v,
121        }
122    }
123}
124
125impl Default for ConfigManagerImpl {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131#[async_trait::async_trait]
132impl ConfigManager for ConfigManagerImpl {
133    async fn get<T: serde::de::DeserializeOwned>(&self, key: &str) -> Result<T> {
134        let data = self.data.read().await;
135
136        // Try to get from data first
137        if let Some(value) = data.get(key) {
138            return serde_json::from_value(value.clone())
139                .map_err(|e| CoreError::JsonError(e.to_string()));
140        }
141
142        // Try to get from defaults
143        if let Some(value) = self.defaults.get(key) {
144            return serde_json::from_value(value.clone())
145                .map_err(|e| CoreError::JsonError(e.to_string()));
146        }
147
148        Err(CoreError::ConfigError(ConfigError::KeyNotFound(
149            key.to_string(),
150        )))
151    }
152
153    async fn set<T: serde::Serialize + Send>(&self, key: &str, value: T) -> Result<()> {
154        let mut data = self.data.write().await;
155        let json_value =
156            serde_json::to_value(value).map_err(|e| CoreError::JsonError(e.to_string()))?;
157
158        data.insert(key.to_string(), json_value);
159        Ok(())
160    }
161
162    async fn delete(&self, key: &str) -> Result<bool> {
163        let mut data = self.data.write().await;
164        Ok(data.remove(key).is_some())
165    }
166
167    async fn exists(&self, key: &str) -> Result<bool> {
168        let data = self.data.read().await;
169        Ok(data.contains_key(key) || self.defaults.contains_key(key))
170    }
171
172    async fn list_keys(&self) -> Result<Vec<String>> {
173        let data = self.data.read().await;
174
175        // Combine keys from data and defaults, removing duplicates
176        let mut keys: Vec<String> = data.keys().cloned().collect();
177
178        for key in self.defaults.keys() {
179            if !keys.contains(key) {
180                keys.push(key.clone());
181            }
182        }
183
184        keys.sort();
185        Ok(keys)
186    }
187
188    async fn load_from_file(&self, path: &Path) -> Result<()> {
189        // Check if file exists
190        if !path.exists() {
191            return Err(CoreError::ConfigError(ConfigError::FileNotFound(
192                path.to_string_lossy().to_string(),
193            )));
194        }
195
196        // Read file content
197        let content = fs::read_to_string(path).map_err(|e| CoreError::IoError(e.to_string()))?;
198
199        // Replace environment variables in the content
200        let content_with_env = self.replace_env_vars(&content);
201
202        // Parse JSON
203        let mut config: HashMap<String, serde_json::Value> =
204            serde_json::from_str(&content_with_env)
205                .map_err(|e| CoreError::ConfigError(ConfigError::ParseFailed(e.to_string())))?;
206
207        // Recursively replace environment variables in JSON values
208        for (_, value) in config.iter_mut() {
209            *value = self.replace_env_vars_in_json(value.clone());
210        }
211
212        // Update data
213        let mut data = self.data.write().await;
214        data.extend(config);
215
216        Ok(())
217    }
218
219    async fn save_to_file(&self, path: &Path) -> Result<()> {
220        let data = self.data.read().await;
221
222        // Create directory if it doesn't exist
223        if let Some(parent) = path.parent() {
224            fs::create_dir_all(parent).map_err(|e| CoreError::IoError(e.to_string()))?;
225        }
226
227        // Write to file
228        let mut file = File::create(path).map_err(|e| CoreError::IoError(e.to_string()))?;
229
230        let json_content = serde_json::to_string_pretty(&*data)
231            .map_err(|e| CoreError::JsonError(e.to_string()))?;
232
233        file.write_all(json_content.as_bytes())
234            .map_err(|e| CoreError::IoError(e.to_string()))?;
235
236        Ok(())
237    }
238
239    async fn reload(&self) -> Result<()> {
240        // Reset data to defaults
241        let mut data = self.data.write().await;
242        *data = self.defaults.clone();
243
244        Ok(())
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use serde_json::json;
252    use std::collections::HashMap;
253    use tempfile::tempdir;
254
255    #[tokio::test]
256    async fn test_config_manager_new() {
257        let config_manager = ConfigManagerImpl::new();
258        assert_eq!(config_manager.list_keys().await.unwrap().len(), 0);
259    }
260
261    #[tokio::test]
262    async fn test_config_manager_with_defaults() {
263        let mut defaults = HashMap::new();
264        defaults.insert("key1".to_string(), json!("value1"));
265        defaults.insert("key2".to_string(), json!(42));
266
267        let config_manager = ConfigManagerImpl::with_defaults(defaults);
268        let keys = config_manager.list_keys().await.unwrap();
269
270        assert_eq!(keys.len(), 2);
271        assert!(keys.contains(&"key1".to_string()));
272        assert!(keys.contains(&"key2".to_string()));
273
274        let value1: String = config_manager.get("key1").await.unwrap();
275        let value2: i64 = config_manager.get("key2").await.unwrap();
276
277        assert_eq!(value1, "value1");
278        assert_eq!(value2, 42);
279    }
280
281    #[tokio::test]
282    async fn test_config_manager_set_get() {
283        let config_manager = ConfigManagerImpl::new();
284
285        // Set values
286        config_manager
287            .set("string_key", "test_value")
288            .await
289            .unwrap();
290        config_manager.set("int_key", 123).await.unwrap();
291        config_manager.set("bool_key", true).await.unwrap();
292
293        // Get values
294        let string_value: String = config_manager.get("string_key").await.unwrap();
295        let int_value: i64 = config_manager.get("int_key").await.unwrap();
296        let bool_value: bool = config_manager.get("bool_key").await.unwrap();
297
298        // Verify values
299        assert_eq!(string_value, "test_value");
300        assert_eq!(int_value, 123);
301        assert_eq!(bool_value, true);
302    }
303
304    #[tokio::test]
305    async fn test_config_manager_delete() {
306        let config_manager = ConfigManagerImpl::new();
307
308        // Set a value
309        config_manager.set("test_key", "test_value").await.unwrap();
310
311        // Verify it exists
312        let exists_before = config_manager.exists("test_key").await.unwrap();
313        assert!(exists_before);
314
315        // Delete it
316        let deleted = config_manager.delete("test_key").await.unwrap();
317        assert!(deleted);
318
319        // Verify it's gone
320        let exists_after = config_manager.exists("test_key").await.unwrap();
321        assert!(!exists_after);
322
323        // Try to delete it again
324        let deleted_again = config_manager.delete("test_key").await.unwrap();
325        assert!(!deleted_again);
326    }
327
328    #[tokio::test]
329    async fn test_config_manager_load_save_file() {
330        let temp_dir = tempdir().unwrap();
331        let config_path = temp_dir.path().join("config.json");
332
333        // Create config manager with some values
334        let config_manager = ConfigManagerImpl::new();
335        config_manager.set("test_key", "test_value").await.unwrap();
336        config_manager.set("another_key", 42).await.unwrap();
337
338        // Save to file
339        config_manager.save_to_file(&config_path).await.unwrap();
340
341        // Create a new config manager and load from file
342        let config_manager2 = ConfigManagerImpl::new();
343        config_manager2.load_from_file(&config_path).await.unwrap();
344
345        // Verify values are loaded correctly
346        let value1: String = config_manager2.get("test_key").await.unwrap();
347        let value2: i64 = config_manager2.get("another_key").await.unwrap();
348
349        assert_eq!(value1, "test_value");
350        assert_eq!(value2, 42);
351    }
352
353    #[tokio::test]
354    async fn test_config_manager_reload() {
355        let mut defaults = HashMap::new();
356        defaults.insert("default_key".to_string(), json!("default_value"));
357
358        let config_manager = ConfigManagerImpl::with_defaults(defaults);
359
360        // Set some values
361        config_manager.set("test_key", "test_value").await.unwrap();
362        config_manager
363            .set("default_key", "modified_value")
364            .await
365            .unwrap();
366
367        // Reload
368        config_manager.reload().await.unwrap();
369
370        // Verify values are reset to defaults
371        let default_value: String = config_manager.get("default_key").await.unwrap();
372        assert_eq!(default_value, "default_value");
373
374        // Verify non-default values are removed
375        let exists = config_manager.exists("test_key").await.unwrap();
376        assert!(!exists);
377    }
378
379    #[tokio::test]
380    async fn test_config_manager_env_vars() {
381        // Set an environment variable for testing
382        std::env::set_var("TEST_ENV_VAR", "env_value");
383
384        let temp_dir = tempdir().unwrap();
385        let config_path = temp_dir.path().join("config.json");
386
387        // Create a config file with environment variable
388        let config_content = r#"{
389            "env_key": "${TEST_ENV_VAR}",
390            "regular_key": "regular_value"
391        }"#;
392        std::fs::write(&config_path, config_content).unwrap();
393
394        // Load from file
395        let config_manager = ConfigManagerImpl::new();
396        config_manager.load_from_file(&config_path).await.unwrap();
397
398        // Verify values
399        let env_value: String = config_manager.get("env_key").await.unwrap();
400        let regular_value: String = config_manager.get("regular_key").await.unwrap();
401
402        assert_eq!(env_value, "env_value");
403        assert_eq!(regular_value, "regular_value");
404
405        // Clean up environment variable
406        std::env::remove_var("TEST_ENV_VAR");
407    }
408}