1use crate::{Error, Result, Value};
2use std::collections::{BTreeMap, HashMap};
3use std::path::Path;
4use std::sync::{Arc, RwLock};
5
6#[derive(Debug, Clone)]
16struct FastCache {
17 hot_values: HashMap<String, Value>,
19 hits: u64,
21 misses: u64,
23}
24
25impl FastCache {
26 fn new() -> Self {
27 Self {
28 hot_values: HashMap::new(),
29 hits: 0,
30 misses: 0,
31 }
32 }
33
34 fn get(&mut self, key: &str) -> Option<&Value> {
35 if let Some(value) = self.hot_values.get(key) {
36 self.hits += 1;
37 Some(value)
38 } else {
39 self.misses += 1;
40 None
41 }
42 }
43
44 fn insert(&mut self, key: String, value: Value) {
45 if self.hot_values.len() >= 100 {
47 let keys_to_remove: Vec<_> = self.hot_values.keys().take(20).cloned().collect();
49 for k in keys_to_remove {
50 self.hot_values.remove(&k);
51 }
52 }
53 self.hot_values.insert(key, value);
54 }
55}
56
57#[derive(Debug)]
105pub struct EnterpriseConfig {
106 fast_cache: Arc<RwLock<FastCache>>,
108 cache: Arc<RwLock<BTreeMap<String, Value>>>,
110 defaults: Arc<RwLock<BTreeMap<String, Value>>>,
112 file_path: Option<String>,
114 format: String,
116 read_only: bool,
118}
119
120#[derive(Debug, Default)]
122pub struct ConfigManager {
123 configs: Arc<RwLock<HashMap<String, EnterpriseConfig>>>,
125}
126
127impl Default for EnterpriseConfig {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl EnterpriseConfig {
134 #[inline(always)]
136 pub fn new() -> Self {
137 Self {
138 fast_cache: Arc::new(RwLock::new(FastCache::new())),
139 cache: Arc::new(RwLock::new(BTreeMap::new())),
140 defaults: Arc::new(RwLock::new(BTreeMap::new())),
141 file_path: None,
142 format: "conf".to_string(),
143 read_only: false,
144 }
145 }
146
147 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
149 let path_str = path.as_ref().to_string_lossy().to_string();
150 let content = std::fs::read_to_string(&path)?;
151
152 let format = Self::detect_format(&path_str);
154 let value = Self::parse_content(&content, &format)?;
155
156 let mut config = Self::new();
157 config.file_path = Some(path_str);
158 config.format = format;
159
160 if let Value::Table(table) = value {
162 if let Ok(mut cache) = config.cache.write() {
163 *cache = table;
164 }
165 }
166
167 Ok(config)
168 }
169
170 pub fn from_string(content: &str, format: Option<&str>) -> Result<Self> {
172 let format = format.unwrap_or("conf").to_string();
173 let value = Self::parse_content(content, &format)?;
174
175 let mut config = Self::new();
176 config.format = format;
177
178 if let Value::Table(table) = value {
180 if let Ok(mut cache) = config.cache.write() {
181 *cache = table;
182 }
183 }
184
185 Ok(config)
186 }
187
188 #[inline(always)]
190 pub fn get(&self, key: &str) -> Option<Value> {
191 if let Ok(mut fast_cache) = self.fast_cache.write() {
193 if let Some(value) = fast_cache.get(key) {
194 return Some(value.clone());
195 }
196 }
197
198 if let Ok(cache) = self.cache.read() {
200 if let Some(value) = self.get_nested(&cache, key) {
201 let value_clone = value.clone();
202 if let Ok(mut fast_cache) = self.fast_cache.write() {
204 fast_cache.insert(key.to_string(), value_clone.clone());
205 }
206 return Some(value_clone);
207 }
208 }
209
210 if let Ok(defaults) = self.defaults.read() {
212 if let Some(value) = self.get_nested(&defaults, key) {
213 let value_clone = value.clone();
214 if let Ok(mut fast_cache) = self.fast_cache.write() {
216 fast_cache.insert(key.to_string(), value_clone.clone());
217 }
218 return Some(value_clone);
219 }
220 }
221
222 None
223 }
224
225 pub fn get_or<T>(&self, key: &str, default: T) -> T
227 where
228 T: From<Value> + Clone,
229 {
230 if let Some(value) = self.get(key) {
231 T::from(value)
233 } else {
234 default
235 }
236 }
237
238 #[inline(always)]
240 pub fn get_or_default(&self, key: &str) -> Option<Value> {
241 if let Some(value) = self.get(key) {
242 Some(value)
243 } else {
244 if let Ok(defaults) = self.defaults.read() {
246 self.get_nested(&defaults, key).cloned()
247 } else {
248 None
249 }
250 }
251 }
252
253 #[inline(always)]
255 pub fn exists(&self, key: &str) -> bool {
256 if let Ok(cache) = self.cache.read() {
258 if self.get_nested(&cache, key).is_some() {
259 return true;
260 }
261 }
262
263 if let Ok(defaults) = self.defaults.read() {
265 self.get_nested(&defaults, key).is_some()
266 } else {
267 false
268 }
269 }
270
271 pub fn set(&mut self, key: &str, value: Value) -> Result<()> {
273 if let Ok(mut cache) = self.cache.write() {
274 self.set_nested(&mut cache, key, value.clone());
275
276 if let Ok(mut fast_cache) = self.fast_cache.write() {
278 fast_cache.hot_values.remove(key);
279 fast_cache.insert(key.to_string(), value);
281 }
282
283 Ok(())
284 } else {
285 Err(Error::general(
286 "Failed to acquire cache lock for write operation",
287 ))
288 }
289 }
290
291 pub fn cache_stats(&self) -> (u64, u64, f64) {
293 if let Ok(fast_cache) = self.fast_cache.read() {
294 let hit_ratio = if fast_cache.hits + fast_cache.misses > 0 {
295 fast_cache.hits as f64 / (fast_cache.hits + fast_cache.misses) as f64
296 } else {
297 0.0
298 };
299 (fast_cache.hits, fast_cache.misses, hit_ratio)
300 } else {
301 (0, 0, 0.0)
303 }
304 }
305
306 pub fn set_default(&mut self, key: &str, value: Value) {
308 if let Ok(mut defaults) = self.defaults.write() {
309 self.set_nested(&mut defaults, key, value);
310 }
311 }
312
313 pub fn save(&self) -> Result<()> {
315 if let Some(ref path) = self.file_path {
316 if let Ok(cache) = self.cache.read() {
317 let content = self.serialize_to_format(&cache, &self.format)?;
318 std::fs::write(path, content)?;
319 Ok(())
320 } else {
321 Err(Error::general(
322 "Failed to acquire cache lock for save operation",
323 ))
324 }
325 } else {
326 Err(Error::general("No file path specified for save"))
327 }
328 }
329
330 pub fn save_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
332 let path_str = path.as_ref().to_string_lossy();
333 let format = Self::detect_format(&path_str);
334 if let Ok(cache) = self.cache.read() {
335 let content = self.serialize_to_format(&cache, &format)?;
336 std::fs::write(path, content)?;
337 Ok(())
338 } else {
339 Err(Error::general(
340 "Failed to acquire cache lock for save operation",
341 ))
342 }
343 }
344
345 pub fn keys(&self) -> Vec<String> {
347 if let Ok(cache) = self.cache.read() {
348 self.collect_keys(&cache, "")
349 } else {
350 Vec::new()
351 }
352 }
353
354 pub fn make_read_only(&mut self) {
356 self.read_only = true;
357 }
358
359 pub fn clear(&mut self) -> Result<()> {
361 if self.read_only {
362 return Err(Error::general("Configuration is read-only"));
363 }
364
365 let mut cache = self
366 .cache
367 .write()
368 .map_err(|_| Error::concurrency("Cache lock poisoned"))?;
369 cache.clear();
370 Ok(())
371 }
372
373 pub fn merge(&mut self, other: &EnterpriseConfig) -> Result<()> {
375 if self.read_only {
376 return Err(Error::general("Configuration is read-only"));
377 }
378 let other_cache = other
380 .cache
381 .read()
382 .map_err(|_| Error::concurrency("Other cache lock poisoned"))?;
383 let mut self_cache = self
384 .cache
385 .write()
386 .map_err(|_| Error::concurrency("Self cache lock poisoned"))?;
387
388 for (key, value) in other_cache.iter() {
390 self_cache.insert(key.clone(), value.clone());
393 }
394
395 Ok(())
396 }
397
398 fn detect_format(path: &str) -> String {
402 if path.ends_with(".json") {
403 "json".to_string()
404 } else if path.ends_with(".toml") {
405 "toml".to_string()
406 } else if path.ends_with(".noml") {
407 "noml".to_string()
408 } else {
409 "conf".to_string()
410 }
411 }
412
413 fn parse_content(content: &str, format: &str) -> Result<Value> {
415 match format {
416 "conf" => {
417 crate::parsers::conf::parse(content)
419 }
420 #[cfg(feature = "json")]
421 "json" => {
422 let parsed: serde_json::Value = serde_json::from_str(content)
423 .map_err(|e| Error::general(format!("JSON parse error: {e}")))?;
424 crate::parsers::json_parser::from_json_value(parsed)
425 }
426 #[cfg(feature = "toml")]
427 "toml" => crate::parsers::toml_parser::parse(content),
428 #[cfg(feature = "noml")]
429 "noml" => crate::parsers::noml_parser::parse(content),
430 _ => Err(Error::general(format!("Unsupported format: {format}"))),
431 }
432 }
433
434 #[inline(always)]
436 fn get_nested<'a>(&self, table: &'a BTreeMap<String, Value>, key: &str) -> Option<&'a Value> {
437 if !key.contains('.') {
438 return table.get(key);
439 }
440
441 let parts: Vec<&str> = key.split('.').collect();
442 let mut current = table.get(parts[0])?;
443
444 for part in &parts[1..] {
445 match current {
446 Value::Table(nested_table) => {
447 current = nested_table.get(*part)?;
448 }
449 _ => return None,
450 }
451 }
452
453 Some(current)
454 }
455
456 fn set_nested(&self, table: &mut BTreeMap<String, Value>, key: &str, value: Value) {
458 if !key.contains('.') {
459 table.insert(key.to_string(), value);
460 return;
461 }
462
463 let parts: Vec<&str> = key.split('.').collect();
464 set_recursive(table, &parts, value);
465 }
466
467 #[allow(clippy::only_used_in_recursion)]
469 fn collect_keys(&self, table: &BTreeMap<String, Value>, prefix: &str) -> Vec<String> {
470 let mut keys = Vec::new();
471
472 for (key, value) in table {
473 let full_key = if prefix.is_empty() {
474 key.clone()
475 } else {
476 format!("{prefix}.{key}")
477 };
478
479 keys.push(full_key.clone());
480
481 if let Value::Table(nested_table) = value {
482 keys.extend(self.collect_keys(nested_table, &full_key));
483 }
484 }
485
486 keys
487 }
488
489 fn serialize_to_format(&self, table: &BTreeMap<String, Value>, format: &str) -> Result<String> {
491 match format {
492 "conf" => {
493 let mut output = String::new();
495 for (key, value) in table {
496 output.push_str(&format!("{} = {}\n", key, self.value_to_string(value)));
497 }
498 Ok(output)
499 }
500 #[cfg(feature = "json")]
501 "json" => {
502 let json_value =
503 crate::parsers::json_parser::to_json_value(&Value::table(table.clone()))?;
504 serde_json::to_string_pretty(&json_value)
505 .map_err(|e| Error::general(format!("JSON serialize error: {e}")))
506 }
507 _ => Err(Error::general(format!(
508 "Serialization not supported for format: {format}"
509 ))),
510 }
511 }
512
513 #[allow(clippy::only_used_in_recursion)]
515 fn value_to_string(&self, value: &Value) -> String {
516 match value {
517 Value::String(s) => format!("\"{s}\""),
518 Value::Integer(i) => i.to_string(),
519 Value::Float(f) => f.to_string(),
520 Value::Bool(b) => b.to_string(),
521 Value::Null => "null".to_string(),
522 Value::Array(arr) => {
523 let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
524 items.join(" ")
525 }
526 Value::Table(_) => "[Table]".to_string(), #[cfg(feature = "chrono")]
528 Value::DateTime(dt) => dt.to_rfc3339(),
529 }
530 }
531}
532
533impl ConfigManager {
534 pub fn new() -> Self {
536 Self::default()
537 }
538
539 pub fn load<P: AsRef<Path>>(&self, name: &str, path: P) -> Result<()> {
541 let config = EnterpriseConfig::from_file(path)?;
542 let mut configs = self
543 .configs
544 .write()
545 .map_err(|_| Error::concurrency("Configs lock poisoned"))?;
546 configs.insert(name.to_string(), config);
547 Ok(())
548 }
549
550 pub fn get(&self, name: &str) -> Option<Arc<RwLock<EnterpriseConfig>>> {
552 let configs = self.configs.read().ok()?;
553 configs.get(name).map(|config| {
554 Arc::new(RwLock::new(EnterpriseConfig {
556 fast_cache: config.fast_cache.clone(),
557 cache: config.cache.clone(),
558 defaults: config.defaults.clone(),
559 file_path: config.file_path.clone(),
560 format: config.format.clone(),
561 read_only: config.read_only,
562 }))
563 })
564 }
565
566 pub fn list(&self) -> Vec<String> {
568 match self.configs.read() {
569 Ok(configs) => configs.keys().cloned().collect(),
570 Err(_) => Vec::new(), }
572 }
573
574 pub fn remove(&self, name: &str) -> bool {
576 match self.configs.write() {
577 Ok(mut configs) => configs.remove(name).is_some(),
578 Err(_) => false, }
580 }
581}
582
583pub mod direct {
586 use super::*;
587
588 #[inline(always)]
590 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Value> {
591 let content = std::fs::read_to_string(path)?;
592 parse_string(&content, None)
593 }
594
595 #[inline(always)]
597 pub fn parse_string(content: &str, format: Option<&str>) -> Result<Value> {
598 let format = format.unwrap_or("conf");
599 EnterpriseConfig::parse_content(content, format)
600 }
601
602 #[inline(always)]
604 pub fn parse_to_vec<T>(content: &str) -> Result<Vec<T>>
605 where
606 T: TryFrom<Value>,
607 T::Error: std::fmt::Display,
608 {
609 let value = parse_string(content, None)?;
610
611 match value {
612 Value::Array(arr) => arr
613 .into_iter()
614 .map(|v| T::try_from(v).map_err(|e| Error::general(e.to_string())))
615 .collect(),
616 _ => Err(Error::general("Expected array value")),
617 }
618 }
619}
620
621fn set_recursive(table: &mut BTreeMap<String, Value>, parts: &[&str], value: Value) {
626 if parts.len() == 1 {
627 table.insert(parts[0].to_string(), value);
628 return;
629 }
630
631 let key = parts[0].to_string();
632 let remaining = &parts[1..];
633
634 if !table.contains_key(&key) {
635 table.insert(key.clone(), Value::table(BTreeMap::new()));
636 }
637
638 if let Some(entry) = table.get_mut(&key) {
639 if !entry.is_table() {
640 *entry = Value::table(BTreeMap::new());
641 }
642 if let Value::Table(nested_table) = entry {
643 set_recursive(nested_table, remaining, value);
644 }
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651
652 #[test]
653 fn test_enterprise_config_get_or() {
654 let mut config = EnterpriseConfig::new();
655 config.set("port", Value::integer(8080)).unwrap();
656
657 if let Some(port_value) = config.get("port") {
659 let port = port_value.as_integer().unwrap_or(3000);
660 assert_eq!(port, 8080);
661 }
662
663 if config.get("timeout").is_some() {
665 panic!("Should not find timeout key");
666 }
667
668 let timeout = config
670 .get("timeout")
671 .and_then(|v| v.as_integer().ok())
672 .unwrap_or(30);
673 assert_eq!(timeout, 30);
674 }
675
676 #[test]
677 fn test_exists() {
678 let mut config = EnterpriseConfig::new();
679 config.set("debug", Value::bool(true)).unwrap();
680
681 assert!(config.exists("debug"));
682 assert!(!config.exists("production"));
683 }
684
685 #[test]
686 fn test_nested_keys() {
687 let mut config = EnterpriseConfig::new();
688 config
689 .set("database.host", Value::string("localhost"))
690 .unwrap();
691 config.set("database.port", Value::integer(5432)).unwrap();
692
693 assert_eq!(
694 config.get("database.host").unwrap().as_string().unwrap(),
695 "localhost"
696 );
697 assert_eq!(
698 config.get("database.port").unwrap().as_integer().unwrap(),
699 5432
700 );
701 assert!(config.exists("database.host"));
702 }
703
704 #[test]
705 fn test_direct_parsing() {
706 let content = "port = 8080\ndebug = true";
707 let value = direct::parse_string(content, Some("conf")).unwrap();
708
709 if let Value::Table(table) = value {
710 assert_eq!(table.get("port").unwrap().as_integer().unwrap(), 8080);
711 assert!(table.get("debug").unwrap().as_bool().unwrap());
712 } else {
713 panic!("Expected table value");
714 }
715 }
716}