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 if let Some(first_key) = self.hot_values.keys().next().cloned() {
49 self.hot_values.remove(&first_key);
50 }
51 }
52 self.hot_values.insert(key, value);
53 }
54}
55
56#[derive(Debug)]
104pub struct EnterpriseConfig {
105 fast_cache: Arc<RwLock<FastCache>>,
107 cache: Arc<RwLock<BTreeMap<String, Value>>>,
109 defaults: Arc<RwLock<BTreeMap<String, Value>>>,
111 file_path: Option<String>,
113 format: String,
115 read_only: bool,
117}
118
119#[derive(Debug, Default)]
121pub struct ConfigManager {
122 configs: Arc<RwLock<HashMap<String, EnterpriseConfig>>>,
124}
125
126impl Default for EnterpriseConfig {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132impl EnterpriseConfig {
133 #[inline(always)]
135 pub fn new() -> Self {
136 Self {
137 fast_cache: Arc::new(RwLock::new(FastCache::new())),
138 cache: Arc::new(RwLock::new(BTreeMap::new())),
139 defaults: Arc::new(RwLock::new(BTreeMap::new())),
140 file_path: None,
141 format: "conf".to_string(),
142 read_only: false,
143 }
144 }
145
146 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
148 let path_str = path.as_ref().to_string_lossy().to_string();
149 let content = std::fs::read_to_string(&path)?;
150
151 let format = Self::detect_format(&path_str);
153 let value = Self::parse_content(&content, &format)?;
154
155 let mut config = Self::new();
156 config.file_path = Some(path_str);
157 config.format = format;
158
159 if let Value::Table(table) = value {
161 if let Ok(mut cache) = config.cache.write() {
162 *cache = table;
163 }
164 }
165
166 Ok(config)
167 }
168
169 pub fn from_string(content: &str, format: Option<&str>) -> Result<Self> {
171 let format = format.unwrap_or("conf").to_string();
172 let value = Self::parse_content(content, &format)?;
173
174 let mut config = Self::new();
175 config.format = format;
176
177 if let Value::Table(table) = value {
179 if let Ok(mut cache) = config.cache.write() {
180 *cache = table;
181 }
182 }
183
184 Ok(config)
185 }
186
187 #[inline(always)]
189 pub fn get(&self, key: &str) -> Option<Value> {
190 {
192 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
199 if let Ok(cache) = self.cache.read() {
201 if let Some(value) = self.get_nested(&cache, key) {
202 let cloned_value = value.clone();
203 if let Ok(mut fast_cache) = self.fast_cache.write() {
205 fast_cache.insert(key.to_string(), cloned_value.clone());
206 }
207 return Some(cloned_value);
208 }
209 }
210
211 if let Ok(defaults) = self.defaults.read() {
213 if let Some(value) = self.get_nested(&defaults, key) {
214 let cloned_value = value.clone();
215 if let Ok(mut fast_cache) = self.fast_cache.write() {
217 fast_cache.insert(key.to_string(), cloned_value.clone());
218 }
219 return Some(cloned_value);
220 }
221 }
222
223 None
224 }
225
226 pub fn get_or<T>(&self, key: &str, default: T) -> T
228 where
229 T: From<Value> + Clone,
230 {
231 if let Some(value) = self.get(key) {
232 T::from(value)
234 } else {
235 default
236 }
237 }
238
239 #[inline(always)]
241 pub fn get_or_default(&self, key: &str) -> Option<Value> {
242 if let Some(value) = self.get(key) {
243 Some(value)
244 } else {
245 if let Ok(defaults) = self.defaults.read() {
247 self.get_nested(&defaults, key).cloned()
248 } else {
249 None
250 }
251 }
252 }
253
254 #[inline(always)]
256 pub fn exists(&self, key: &str) -> bool {
257 if let Ok(cache) = self.cache.read() {
259 if self.get_nested(&cache, key).is_some() {
260 return true;
261 }
262 }
263
264 if let Ok(defaults) = self.defaults.read() {
266 self.get_nested(&defaults, key).is_some()
267 } else {
268 false
269 }
270 }
271
272 pub fn set(&mut self, key: &str, value: Value) -> Result<()> {
274 if let Ok(mut cache) = self.cache.write() {
275 self.set_nested(&mut cache, key, value.clone());
276
277 if let Ok(mut fast_cache) = self.fast_cache.write() {
279 fast_cache.hot_values.remove(key);
280 fast_cache.insert(key.to_string(), value);
282 }
283
284 Ok(())
285 } else {
286 Err(Error::general(
287 "Failed to acquire cache lock for write operation",
288 ))
289 }
290 }
291
292 pub fn cache_stats(&self) -> (u64, u64, f64) {
294 if let Ok(fast_cache) = self.fast_cache.read() {
295 let hit_ratio = if fast_cache.hits + fast_cache.misses > 0 {
296 fast_cache.hits as f64 / (fast_cache.hits + fast_cache.misses) as f64
297 } else {
298 0.0
299 };
300 (fast_cache.hits, fast_cache.misses, hit_ratio)
301 } else {
302 (0, 0, 0.0)
304 }
305 }
306
307 pub fn set_default(&mut self, key: &str, value: Value) {
309 if let Ok(mut defaults) = self.defaults.write() {
310 self.set_nested(&mut defaults, key, value);
311 }
312 }
313
314 pub fn save(&self) -> Result<()> {
316 if let Some(ref path) = self.file_path {
317 if let Ok(cache) = self.cache.read() {
318 let content = self.serialize_to_format(&cache, &self.format)?;
319 std::fs::write(path, content)?;
320 Ok(())
321 } else {
322 Err(Error::general(
323 "Failed to acquire cache lock for save operation",
324 ))
325 }
326 } else {
327 Err(Error::general("No file path specified for save"))
328 }
329 }
330
331 pub fn save_to<P: AsRef<Path>>(&self, path: P) -> Result<()> {
333 let path_str = path.as_ref().to_string_lossy();
334 let format = Self::detect_format(&path_str);
335 if let Ok(cache) = self.cache.read() {
336 let content = self.serialize_to_format(&cache, &format)?;
337 std::fs::write(path, content)?;
338 Ok(())
339 } else {
340 Err(Error::general(
341 "Failed to acquire cache lock for save operation",
342 ))
343 }
344 }
345
346 pub fn keys(&self) -> Vec<String> {
348 if let Ok(cache) = self.cache.read() {
349 self.collect_keys(&cache, "")
350 } else {
351 Vec::new()
352 }
353 }
354
355 pub fn make_read_only(&mut self) {
357 self.read_only = true;
358 }
359
360 pub fn clear(&mut self) -> Result<()> {
362 if self.read_only {
363 return Err(Error::general("Configuration is read-only"));
364 }
365
366 let mut cache = self.cache.write().unwrap();
367 cache.clear();
368 Ok(())
369 }
370
371 pub fn merge(&mut self, other: &EnterpriseConfig) -> Result<()> {
373 if self.read_only {
374 return Err(Error::general("Configuration is read-only"));
375 }
376 let other_cache = other.cache.read().unwrap();
378 let mut self_cache = self.cache.write().unwrap();
379
380 for (key, value) in other_cache.iter() {
382 self_cache.insert(key.clone(), value.clone());
385 }
386
387 Ok(())
388 }
389
390 fn detect_format(path: &str) -> String {
394 if path.ends_with(".json") {
395 "json".to_string()
396 } else if path.ends_with(".toml") {
397 "toml".to_string()
398 } else if path.ends_with(".noml") {
399 "noml".to_string()
400 } else {
401 "conf".to_string()
402 }
403 }
404
405 fn parse_content(content: &str, format: &str) -> Result<Value> {
407 match format {
408 "conf" => {
409 crate::parsers::conf::parse(content)
411 }
412 #[cfg(feature = "json")]
413 "json" => {
414 let parsed: serde_json::Value = serde_json::from_str(content)
415 .map_err(|e| Error::general(format!("JSON parse error: {e}")))?;
416 crate::parsers::json_parser::from_json_value(parsed)
417 }
418 #[cfg(feature = "toml")]
419 "toml" => crate::parsers::toml_parser::parse(content),
420 #[cfg(feature = "noml")]
421 "noml" => crate::parsers::noml_parser::parse(content),
422 _ => Err(Error::general(format!("Unsupported format: {format}"))),
423 }
424 }
425
426 #[inline(always)]
428 fn get_nested<'a>(&self, table: &'a BTreeMap<String, Value>, key: &str) -> Option<&'a Value> {
429 if !key.contains('.') {
430 return table.get(key);
431 }
432
433 let parts: Vec<&str> = key.split('.').collect();
434 let mut current = table.get(parts[0])?;
435
436 for part in &parts[1..] {
437 match current {
438 Value::Table(nested_table) => {
439 current = nested_table.get(*part)?;
440 }
441 _ => return None,
442 }
443 }
444
445 Some(current)
446 }
447
448 fn set_nested(&self, table: &mut BTreeMap<String, Value>, key: &str, value: Value) {
450 if !key.contains('.') {
451 table.insert(key.to_string(), value);
452 return;
453 }
454
455 let parts: Vec<&str> = key.split('.').collect();
456
457 fn set_recursive(table: &mut BTreeMap<String, Value>, parts: &[&str], value: Value) {
459 if parts.len() == 1 {
460 table.insert(parts[0].to_string(), value);
461 return;
462 }
463
464 let key = parts[0].to_string();
465 let remaining = &parts[1..];
466
467 if !table.contains_key(&key) {
469 table.insert(key.clone(), Value::table(BTreeMap::new()));
470 }
471
472 let entry = table.get_mut(&key).unwrap();
473 if !entry.is_table() {
474 *entry = Value::table(BTreeMap::new());
475 }
476
477 if let Value::Table(nested_table) = entry {
478 set_recursive(nested_table, remaining, value);
479 }
480 }
481
482 set_recursive(table, &parts, value);
483 }
484
485 #[allow(clippy::only_used_in_recursion)]
487 fn collect_keys(&self, table: &BTreeMap<String, Value>, prefix: &str) -> Vec<String> {
488 let mut keys = Vec::new();
489
490 for (key, value) in table {
491 let full_key = if prefix.is_empty() {
492 key.clone()
493 } else {
494 format!("{prefix}.{key}")
495 };
496
497 keys.push(full_key.clone());
498
499 if let Value::Table(nested_table) = value {
500 keys.extend(self.collect_keys(nested_table, &full_key));
501 }
502 }
503
504 keys
505 }
506
507 fn serialize_to_format(&self, table: &BTreeMap<String, Value>, format: &str) -> Result<String> {
509 match format {
510 "conf" => {
511 let mut output = String::new();
513 for (key, value) in table {
514 output.push_str(&format!("{} = {}\n", key, self.value_to_string(value)));
515 }
516 Ok(output)
517 }
518 #[cfg(feature = "json")]
519 "json" => {
520 let json_value =
521 crate::parsers::json_parser::to_json_value(&Value::table(table.clone()))?;
522 serde_json::to_string_pretty(&json_value)
523 .map_err(|e| Error::general(format!("JSON serialize error: {e}")))
524 }
525 _ => Err(Error::general(format!(
526 "Serialization not supported for format: {format}"
527 ))),
528 }
529 }
530
531 #[allow(clippy::only_used_in_recursion)]
533 fn value_to_string(&self, value: &Value) -> String {
534 match value {
535 Value::String(s) => format!("\"{s}\""),
536 Value::Integer(i) => i.to_string(),
537 Value::Float(f) => f.to_string(),
538 Value::Bool(b) => b.to_string(),
539 Value::Null => "null".to_string(),
540 Value::Array(arr) => {
541 let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
542 items.join(" ")
543 }
544 Value::Table(_) => "[Table]".to_string(), #[cfg(feature = "chrono")]
546 Value::DateTime(dt) => dt.to_rfc3339(),
547 }
548 }
549}
550
551impl ConfigManager {
552 pub fn new() -> Self {
554 Self::default()
555 }
556
557 pub fn load<P: AsRef<Path>>(&self, name: &str, path: P) -> Result<()> {
559 let config = EnterpriseConfig::from_file(path)?;
560 let mut configs = self.configs.write().unwrap();
561 configs.insert(name.to_string(), config);
562 Ok(())
563 }
564
565 pub fn get(&self, name: &str) -> Option<Arc<RwLock<EnterpriseConfig>>> {
567 let configs = self.configs.read().unwrap();
568 configs.get(name).map(|config| {
569 Arc::new(RwLock::new(EnterpriseConfig {
571 fast_cache: config.fast_cache.clone(),
572 cache: config.cache.clone(),
573 defaults: config.defaults.clone(),
574 file_path: config.file_path.clone(),
575 format: config.format.clone(),
576 read_only: config.read_only,
577 }))
578 })
579 }
580
581 pub fn list(&self) -> Vec<String> {
583 let configs = self.configs.read().unwrap();
584 configs.keys().cloned().collect()
585 }
586
587 pub fn remove(&self, name: &str) -> bool {
589 let mut configs = self.configs.write().unwrap();
590 configs.remove(name).is_some()
591 }
592}
593
594pub mod direct {
597 use super::*;
598
599 #[inline(always)]
601 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Value> {
602 let content = std::fs::read_to_string(path)?;
603 parse_string(&content, None)
604 }
605
606 #[inline(always)]
608 pub fn parse_string(content: &str, format: Option<&str>) -> Result<Value> {
609 let format = format.unwrap_or("conf");
610 EnterpriseConfig::parse_content(content, format)
611 }
612
613 #[inline(always)]
615 pub fn parse_to_vec<T>(content: &str) -> Result<Vec<T>>
616 where
617 T: TryFrom<Value>,
618 T::Error: std::fmt::Display,
619 {
620 let value = parse_string(content, None)?;
621
622 match value {
623 Value::Array(arr) => arr
624 .into_iter()
625 .map(|v| T::try_from(v).map_err(|e| Error::general(e.to_string())))
626 .collect(),
627 _ => Err(Error::general("Expected array value")),
628 }
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635
636 #[test]
637 fn test_enterprise_config_get_or() {
638 let mut config = EnterpriseConfig::new();
639 config.set("port", Value::integer(8080)).unwrap();
640
641 if let Some(port_value) = config.get("port") {
643 let port = port_value.as_integer().unwrap_or(3000);
644 assert_eq!(port, 8080);
645 }
646
647 if config.get("timeout").is_some() {
649 panic!("Should not find timeout key");
650 }
651
652 let timeout = config
654 .get("timeout")
655 .and_then(|v| v.as_integer().ok())
656 .unwrap_or(30);
657 assert_eq!(timeout, 30);
658 }
659
660 #[test]
661 fn test_exists() {
662 let mut config = EnterpriseConfig::new();
663 config.set("debug", Value::bool(true)).unwrap();
664
665 assert!(config.exists("debug"));
666 assert!(!config.exists("production"));
667 }
668
669 #[test]
670 fn test_nested_keys() {
671 let mut config = EnterpriseConfig::new();
672 config
673 .set("database.host", Value::string("localhost"))
674 .unwrap();
675 config.set("database.port", Value::integer(5432)).unwrap();
676
677 assert_eq!(
678 config.get("database.host").unwrap().as_string().unwrap(),
679 "localhost"
680 );
681 assert_eq!(
682 config.get("database.port").unwrap().as_integer().unwrap(),
683 5432
684 );
685 assert!(config.exists("database.host"));
686 }
687
688 #[test]
689 fn test_direct_parsing() {
690 let content = "port = 8080\ndebug = true";
691 let value = direct::parse_string(content, Some("conf")).unwrap();
692
693 if let Value::Table(table) = value {
694 assert_eq!(table.get("port").unwrap().as_integer().unwrap(), 8080);
695 assert!(table.get("debug").unwrap().as_bool().unwrap());
696 } else {
697 panic!("Expected table value");
698 }
699 }
700}