Skip to main content

litellm_rs/utils/config/
optimized.rs

1//! Optimized configuration management
2//!
3//! This module provides optimized configuration loading and management
4//! with better performance and reduced memory usage.
5//!
6//! Hot reload is not supported. Due to a type-erasure limitation in the generic
7//! interface, `load_config` currently reloads from disk on every call rather than
8//! serving from cache. Restart the server to pick up configuration changes.
9
10use crate::utils::error::gateway_error::{GatewayError, Result};
11use parking_lot::RwLock;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::path::Path;
15use std::sync::Arc;
16use std::sync::LazyLock;
17
18/// Optimized configuration manager with caching support
19#[derive(Debug)]
20pub struct OptimizedConfigManager {
21    /// Cached configurations
22    cache: Arc<RwLock<HashMap<String, Arc<ConfigValue>>>>,
23}
24
25/// Generic configuration value wrapper
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub enum ConfigValue {
28    String(String),
29    Integer(i64),
30    Float(f64),
31    Boolean(bool),
32    Array(Vec<ConfigValue>),
33    Object(HashMap<String, ConfigValue>),
34}
35
36impl ConfigValue {
37    /// Convert to string if possible
38    pub fn as_string(&self) -> Option<&str> {
39        match self {
40            ConfigValue::String(s) => Some(s),
41            _ => None,
42        }
43    }
44
45    /// Convert to integer if possible
46    pub fn as_i64(&self) -> Option<i64> {
47        match self {
48            ConfigValue::Integer(i) => Some(*i),
49            _ => None,
50        }
51    }
52
53    /// Convert to float if possible
54    pub fn as_f64(&self) -> Option<f64> {
55        match self {
56            ConfigValue::Float(f) => Some(*f),
57            ConfigValue::Integer(i) => Some(*i as f64),
58            _ => None,
59        }
60    }
61
62    /// Convert to boolean if possible
63    pub fn as_bool(&self) -> Option<bool> {
64        match self {
65            ConfigValue::Boolean(b) => Some(*b),
66            _ => None,
67        }
68    }
69
70    /// Get nested value by path (e.g., "database.host")
71    pub fn get_nested(&self, path: &str) -> Option<&ConfigValue> {
72        let parts: Vec<&str> = path.split('.').collect();
73        let mut current = self;
74
75        for part in parts {
76            match current {
77                ConfigValue::Object(map) => {
78                    current = map.get(part)?;
79                }
80                ConfigValue::Array(arr) => {
81                    let index: usize = part.parse().ok()?;
82                    current = arr.get(index)?;
83                }
84                _ => return None,
85            }
86        }
87
88        Some(current)
89    }
90}
91
92impl OptimizedConfigManager {
93    /// Create a new configuration manager
94    pub fn new() -> Self {
95        Self {
96            cache: Arc::new(RwLock::new(HashMap::new())),
97        }
98    }
99
100    /// Load configuration from file with caching
101    pub async fn load_config<T>(&self, file_path: &str) -> Result<Arc<T>>
102    where
103        T: for<'de> Deserialize<'de> + Serialize + Send + Sync + 'static,
104    {
105        // Check cache first
106        {
107            let cache = self.cache.read();
108            if let Some(cached) = cache.get(file_path)
109                && let Ok(config) = self.try_downcast_config::<T>(cached.clone())
110            {
111                return Ok(config);
112            }
113        }
114
115        // Load from file
116        let config = self.load_from_file::<T>(file_path).await?;
117        let config_arc = Arc::new(config);
118
119        // Cache the result
120        {
121            let mut cache = self.cache.write();
122            // Store as ConfigValue for generic caching
123            let config_value = self.serialize_to_config_value(&*config_arc)?;
124            cache.insert(file_path.to_string(), Arc::new(config_value));
125        }
126
127        Ok(config_arc)
128    }
129
130    /// Load configuration from file without caching
131    async fn load_from_file<T>(&self, file_path: &str) -> Result<T>
132    where
133        T: for<'de> Deserialize<'de>,
134    {
135        let content = tokio::fs::read_to_string(file_path).await.map_err(|e| {
136            GatewayError::Config(format!("Failed to read config file {}: {}", file_path, e))
137        })?;
138
139        let extension = Path::new(file_path)
140            .extension()
141            .and_then(|ext| ext.to_str())
142            .unwrap_or("yaml");
143
144        match extension {
145            "yaml" | "yml" => serde_yml::from_str(&content)
146                .map_err(|e| GatewayError::Config(format!("Failed to parse YAML config: {}", e))),
147            "json" => serde_json::from_str(&content)
148                .map_err(|e| GatewayError::Config(format!("Failed to parse JSON config: {}", e))),
149            "toml" => {
150                // TOML support would require adding toml crate to dependencies
151                Err(GatewayError::Config("TOML support not enabled".to_string()))
152            }
153            _ => Err(GatewayError::Config(format!(
154                "Unsupported config format: {}",
155                extension
156            ))),
157        }
158    }
159
160    /// Clear cache for a specific file
161    pub fn clear_cache(&self, file_path: &str) {
162        let mut cache = self.cache.write();
163        cache.remove(file_path);
164    }
165
166    /// Clear all cached configurations
167    pub fn clear_all_cache(&self) {
168        let mut cache = self.cache.write();
169        cache.clear();
170    }
171
172    /// Get cache statistics
173    pub fn get_cache_stats(&self) -> HashMap<String, usize> {
174        let cache = self.cache.read();
175        cache
176            .iter()
177            .map(|(k, v)| (k.clone(), std::mem::size_of_val(&**v)))
178            .collect()
179    }
180
181    // Helper methods
182    fn try_downcast_config<T>(&self, _config_value: Arc<ConfigValue>) -> Result<Arc<T>>
183    where
184        T: for<'de> Deserialize<'de> + Send + Sync + 'static,
185    {
186        // This would require more complex type erasure in a real implementation
187        Err(GatewayError::Config(
188            "Type downcast not implemented".to_string(),
189        ))
190    }
191
192    fn serialize_to_config_value<T>(&self, _config: &T) -> Result<ConfigValue>
193    where
194        T: Serialize,
195    {
196        // Simplified implementation
197        Ok(ConfigValue::Object(HashMap::new()))
198    }
199}
200
201impl Default for OptimizedConfigManager {
202    fn default() -> Self {
203        Self::new()
204    }
205}
206
207/// Global configuration manager instance
208pub static GLOBAL_CONFIG_MANAGER: LazyLock<OptimizedConfigManager> =
209    LazyLock::new(OptimizedConfigManager::new);
210
211/// Convenience function to load configuration
212pub async fn load_config<T>(file_path: &str) -> Result<Arc<T>>
213where
214    T: for<'de> Deserialize<'de> + Serialize + Send + Sync + 'static,
215{
216    GLOBAL_CONFIG_MANAGER.load_config(file_path).await
217}
218
219/// Configuration presets for common scenarios
220pub struct ConfigPresets;
221
222impl ConfigPresets {
223    /// Development configuration preset
224    pub fn development() -> HashMap<String, ConfigValue> {
225        let mut config = HashMap::new();
226        config.insert(
227            "log_level".to_string(),
228            ConfigValue::String("debug".to_string()),
229        );
230        config.insert("cache_size".to_string(), ConfigValue::Integer(1000));
231        config.insert("enable_metrics".to_string(), ConfigValue::Boolean(true));
232        config
233    }
234
235    /// Production configuration preset
236    pub fn production() -> HashMap<String, ConfigValue> {
237        let mut config = HashMap::new();
238        config.insert(
239            "log_level".to_string(),
240            ConfigValue::String("info".to_string()),
241        );
242        config.insert("cache_size".to_string(), ConfigValue::Integer(10000));
243        config.insert("enable_metrics".to_string(), ConfigValue::Boolean(true));
244        config
245    }
246
247    /// Testing configuration preset
248    pub fn testing() -> HashMap<String, ConfigValue> {
249        let mut config = HashMap::new();
250        config.insert(
251            "log_level".to_string(),
252            ConfigValue::String("warn".to_string()),
253        );
254        config.insert("cache_size".to_string(), ConfigValue::Integer(100));
255        config.insert("enable_metrics".to_string(), ConfigValue::Boolean(false));
256        config
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_config_value_navigation() {
266        let mut root = HashMap::new();
267        let mut database = HashMap::new();
268        database.insert(
269            "host".to_string(),
270            ConfigValue::String("localhost".to_string()),
271        );
272        database.insert("port".to_string(), ConfigValue::Integer(5432));
273
274        root.insert("database".to_string(), ConfigValue::Object(database));
275        let config = ConfigValue::Object(root);
276
277        assert_eq!(
278            config
279                .get_nested("database.host")
280                .and_then(|v| v.as_string()),
281            Some("localhost")
282        );
283        assert_eq!(
284            config.get_nested("database.port").and_then(|v| v.as_i64()),
285            Some(5432)
286        );
287    }
288
289    #[test]
290    fn test_config_presets() {
291        let dev_config = ConfigPresets::development();
292        assert_eq!(
293            dev_config.get("log_level").and_then(|v| v.as_string()),
294            Some("debug")
295        );
296
297        let prod_config = ConfigPresets::production();
298        assert_eq!(
299            prod_config.get("cache_size").and_then(|v| v.as_i64()),
300            Some(10000)
301        );
302    }
303}