litellm_rs/utils/config/
optimized.rs1use 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#[derive(Debug)]
20pub struct OptimizedConfigManager {
21 cache: Arc<RwLock<HashMap<String, Arc<ConfigValue>>>>,
23}
24
25#[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 pub fn as_string(&self) -> Option<&str> {
39 match self {
40 ConfigValue::String(s) => Some(s),
41 _ => None,
42 }
43 }
44
45 pub fn as_i64(&self) -> Option<i64> {
47 match self {
48 ConfigValue::Integer(i) => Some(*i),
49 _ => None,
50 }
51 }
52
53 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 pub fn as_bool(&self) -> Option<bool> {
64 match self {
65 ConfigValue::Boolean(b) => Some(*b),
66 _ => None,
67 }
68 }
69
70 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 pub fn new() -> Self {
95 Self {
96 cache: Arc::new(RwLock::new(HashMap::new())),
97 }
98 }
99
100 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 {
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 let config = self.load_from_file::<T>(file_path).await?;
117 let config_arc = Arc::new(config);
118
119 {
121 let mut cache = self.cache.write();
122 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 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 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 pub fn clear_cache(&self, file_path: &str) {
162 let mut cache = self.cache.write();
163 cache.remove(file_path);
164 }
165
166 pub fn clear_all_cache(&self) {
168 let mut cache = self.cache.write();
169 cache.clear();
170 }
171
172 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 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 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 Ok(ConfigValue::Object(HashMap::new()))
198 }
199}
200
201impl Default for OptimizedConfigManager {
202 fn default() -> Self {
203 Self::new()
204 }
205}
206
207pub static GLOBAL_CONFIG_MANAGER: LazyLock<OptimizedConfigManager> =
209 LazyLock::new(OptimizedConfigManager::new);
210
211pub 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
219pub struct ConfigPresets;
221
222impl ConfigPresets {
223 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 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 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}