vidsage_core/config/
manager.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum ConfigValue {
15 String(String),
17
18 Integer(i64),
20
21 Float(f64),
23
24 Boolean(bool),
26
27 Array(Vec<ConfigValue>),
29
30 Object(HashMap<String, ConfigValue>),
32
33 Null,
35}
36
37pub struct ConfigManagerImpl {
39 data: RwLock<HashMap<String, serde_json::Value>>,
41
42 defaults: HashMap<String, serde_json::Value>,
44}
45
46impl ConfigManagerImpl {
47 pub fn new() -> Self {
49 Self {
50 data: RwLock::new(HashMap::new()),
51 defaults: HashMap::new(),
52 }
53 }
54
55 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 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 chars.next();
72
73 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 if let Ok(var_value) = std::env::var(&var_name) {
85 result.push_str(&var_value);
86 } else {
87 result.push_str(&format!("${{{}}}", var_name));
89 }
90 } else {
91 result.push(c);
92 }
93 }
94
95 result
96 }
97
98 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 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 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 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 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 if !path.exists() {
191 return Err(CoreError::ConfigError(ConfigError::FileNotFound(
192 path.to_string_lossy().to_string(),
193 )));
194 }
195
196 let content = fs::read_to_string(path).map_err(|e| CoreError::IoError(e.to_string()))?;
198
199 let content_with_env = self.replace_env_vars(&content);
201
202 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 for (_, value) in config.iter_mut() {
209 *value = self.replace_env_vars_in_json(value.clone());
210 }
211
212 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 if let Some(parent) = path.parent() {
224 fs::create_dir_all(parent).map_err(|e| CoreError::IoError(e.to_string()))?;
225 }
226
227 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 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 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 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 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 config_manager.set("test_key", "test_value").await.unwrap();
310
311 let exists_before = config_manager.exists("test_key").await.unwrap();
313 assert!(exists_before);
314
315 let deleted = config_manager.delete("test_key").await.unwrap();
317 assert!(deleted);
318
319 let exists_after = config_manager.exists("test_key").await.unwrap();
321 assert!(!exists_after);
322
323 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 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 config_manager.save_to_file(&config_path).await.unwrap();
340
341 let config_manager2 = ConfigManagerImpl::new();
343 config_manager2.load_from_file(&config_path).await.unwrap();
344
345 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 config_manager.set("test_key", "test_value").await.unwrap();
362 config_manager
363 .set("default_key", "modified_value")
364 .await
365 .unwrap();
366
367 config_manager.reload().await.unwrap();
369
370 let default_value: String = config_manager.get("default_key").await.unwrap();
372 assert_eq!(default_value, "default_value");
373
374 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 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 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 let config_manager = ConfigManagerImpl::new();
396 config_manager.load_from_file(&config_path).await.unwrap();
397
398 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 std::env::remove_var("TEST_ENV_VAR");
407 }
408}