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
465 fn set_recursive(table: &mut BTreeMap<String, Value>, parts: &[&str], value: Value) {
467 if parts.len() == 1 {
468 table.insert(parts[0].to_string(), value);
469 return;
470 }
471
472 let key = parts[0].to_string();
473 let remaining = &parts[1..];
474
475 if !table.contains_key(&key) {
477 table.insert(key.clone(), Value::table(BTreeMap::new()));
478 }
479
480 if let Some(entry) = table.get_mut(&key) {
482 if !entry.is_table() {
483 *entry = Value::table(BTreeMap::new());
484 }
485
486 if let Value::Table(nested_table) = entry {
487 set_recursive(nested_table, remaining, value);
488 }
489 }
490 }
491
492 set_recursive(table, &parts, value);
493 }
494
495 #[allow(clippy::only_used_in_recursion)]
497 fn collect_keys(&self, table: &BTreeMap<String, Value>, prefix: &str) -> Vec<String> {
498 let mut keys = Vec::new();
499
500 for (key, value) in table {
501 let full_key = if prefix.is_empty() {
502 key.clone()
503 } else {
504 format!("{prefix}.{key}")
505 };
506
507 keys.push(full_key.clone());
508
509 if let Value::Table(nested_table) = value {
510 keys.extend(self.collect_keys(nested_table, &full_key));
511 }
512 }
513
514 keys
515 }
516
517 fn serialize_to_format(&self, table: &BTreeMap<String, Value>, format: &str) -> Result<String> {
519 match format {
520 "conf" => {
521 let mut output = String::new();
523 for (key, value) in table {
524 output.push_str(&format!("{} = {}\n", key, self.value_to_string(value)));
525 }
526 Ok(output)
527 }
528 #[cfg(feature = "json")]
529 "json" => {
530 let json_value =
531 crate::parsers::json_parser::to_json_value(&Value::table(table.clone()))?;
532 serde_json::to_string_pretty(&json_value)
533 .map_err(|e| Error::general(format!("JSON serialize error: {e}")))
534 }
535 _ => Err(Error::general(format!(
536 "Serialization not supported for format: {format}"
537 ))),
538 }
539 }
540
541 #[allow(clippy::only_used_in_recursion)]
543 fn value_to_string(&self, value: &Value) -> String {
544 match value {
545 Value::String(s) => format!("\"{s}\""),
546 Value::Integer(i) => i.to_string(),
547 Value::Float(f) => f.to_string(),
548 Value::Bool(b) => b.to_string(),
549 Value::Null => "null".to_string(),
550 Value::Array(arr) => {
551 let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
552 items.join(" ")
553 }
554 Value::Table(_) => "[Table]".to_string(), #[cfg(feature = "chrono")]
556 Value::DateTime(dt) => dt.to_rfc3339(),
557 }
558 }
559}
560
561impl ConfigManager {
562 pub fn new() -> Self {
564 Self::default()
565 }
566
567 pub fn load<P: AsRef<Path>>(&self, name: &str, path: P) -> Result<()> {
569 let config = EnterpriseConfig::from_file(path)?;
570 let mut configs = self
571 .configs
572 .write()
573 .map_err(|_| Error::concurrency("Configs lock poisoned"))?;
574 configs.insert(name.to_string(), config);
575 Ok(())
576 }
577
578 pub fn get(&self, name: &str) -> Option<Arc<RwLock<EnterpriseConfig>>> {
580 let configs = self.configs.read().ok()?;
581 configs.get(name).map(|config| {
582 Arc::new(RwLock::new(EnterpriseConfig {
584 fast_cache: config.fast_cache.clone(),
585 cache: config.cache.clone(),
586 defaults: config.defaults.clone(),
587 file_path: config.file_path.clone(),
588 format: config.format.clone(),
589 read_only: config.read_only,
590 }))
591 })
592 }
593
594 pub fn list(&self) -> Vec<String> {
596 match self.configs.read() {
597 Ok(configs) => configs.keys().cloned().collect(),
598 Err(_) => Vec::new(), }
600 }
601
602 pub fn remove(&self, name: &str) -> bool {
604 match self.configs.write() {
605 Ok(mut configs) => configs.remove(name).is_some(),
606 Err(_) => false, }
608 }
609}
610
611pub mod direct {
614 use super::*;
615
616 #[inline(always)]
618 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Value> {
619 let content = std::fs::read_to_string(path)?;
620 parse_string(&content, None)
621 }
622
623 #[inline(always)]
625 pub fn parse_string(content: &str, format: Option<&str>) -> Result<Value> {
626 let format = format.unwrap_or("conf");
627 EnterpriseConfig::parse_content(content, format)
628 }
629
630 #[inline(always)]
632 pub fn parse_to_vec<T>(content: &str) -> Result<Vec<T>>
633 where
634 T: TryFrom<Value>,
635 T::Error: std::fmt::Display,
636 {
637 let value = parse_string(content, None)?;
638
639 match value {
640 Value::Array(arr) => arr
641 .into_iter()
642 .map(|v| T::try_from(v).map_err(|e| Error::general(e.to_string())))
643 .collect(),
644 _ => Err(Error::general("Expected array value")),
645 }
646 }
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 #[test]
654 fn test_enterprise_config_get_or() {
655 let mut config = EnterpriseConfig::new();
656 config.set("port", Value::integer(8080)).unwrap();
657
658 if let Some(port_value) = config.get("port") {
660 let port = port_value.as_integer().unwrap_or(3000);
661 assert_eq!(port, 8080);
662 }
663
664 if config.get("timeout").is_some() {
666 panic!("Should not find timeout key");
667 }
668
669 let timeout = config
671 .get("timeout")
672 .and_then(|v| v.as_integer().ok())
673 .unwrap_or(30);
674 assert_eq!(timeout, 30);
675 }
676
677 #[test]
678 fn test_exists() {
679 let mut config = EnterpriseConfig::new();
680 config.set("debug", Value::bool(true)).unwrap();
681
682 assert!(config.exists("debug"));
683 assert!(!config.exists("production"));
684 }
685
686 #[test]
687 fn test_nested_keys() {
688 let mut config = EnterpriseConfig::new();
689 config
690 .set("database.host", Value::string("localhost"))
691 .unwrap();
692 config.set("database.port", Value::integer(5432)).unwrap();
693
694 assert_eq!(
695 config.get("database.host").unwrap().as_string().unwrap(),
696 "localhost"
697 );
698 assert_eq!(
699 config.get("database.port").unwrap().as_integer().unwrap(),
700 5432
701 );
702 assert!(config.exists("database.host"));
703 }
704
705 #[test]
706 fn test_direct_parsing() {
707 let content = "port = 8080\ndebug = true";
708 let value = direct::parse_string(content, Some("conf")).unwrap();
709
710 if let Value::Table(table) = value {
711 assert_eq!(table.get("port").unwrap().as_integer().unwrap(), 8080);
712 assert!(table.get("debug").unwrap().as_bool().unwrap());
713 } else {
714 panic!("Expected table value");
715 }
716 }
717}