1use crate::UtilsError;
7use serde::{Deserialize, Serialize};
8use serde_json;
9use std::collections::HashMap;
10use std::env;
11use std::fs;
12use std::path::{Path, PathBuf};
13use std::sync::{Arc, RwLock};
14use std::time::{Duration, SystemTime};
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(untagged)]
19pub enum ConfigValue {
20 String(String),
21 Integer(i64),
22 Float(f64),
23 Boolean(bool),
24 Array(Vec<ConfigValue>),
25 Object(HashMap<String, ConfigValue>),
26 Null,
27}
28
29impl ConfigValue {
30 pub fn as_string(&self) -> Option<String> {
32 match self {
33 ConfigValue::String(s) => Some(s.clone()),
34 ConfigValue::Integer(i) => Some(i.to_string()),
35 ConfigValue::Float(f) => Some(f.to_string()),
36 ConfigValue::Boolean(b) => Some(b.to_string()),
37 _ => None,
38 }
39 }
40
41 pub fn as_i64(&self) -> Option<i64> {
43 match self {
44 ConfigValue::Integer(i) => Some(*i),
45 ConfigValue::Float(f) => Some(*f as i64),
46 ConfigValue::String(s) => s.parse().ok(),
47 _ => None,
48 }
49 }
50
51 pub fn as_f64(&self) -> Option<f64> {
53 match self {
54 ConfigValue::Float(f) => Some(*f),
55 ConfigValue::Integer(i) => Some(*i as f64),
56 ConfigValue::String(s) => s.parse().ok(),
57 _ => None,
58 }
59 }
60
61 pub fn as_bool(&self) -> Option<bool> {
63 match self {
64 ConfigValue::Boolean(b) => Some(*b),
65 ConfigValue::String(s) => match s.to_lowercase().as_str() {
66 "true" | "yes" | "on" | "1" => Some(true),
67 "false" | "no" | "off" | "0" => Some(false),
68 _ => None,
69 },
70 ConfigValue::Integer(i) => Some(*i != 0),
71 _ => None,
72 }
73 }
74
75 pub fn as_array(&self) -> Option<&Vec<ConfigValue>> {
77 match self {
78 ConfigValue::Array(arr) => Some(arr),
79 _ => None,
80 }
81 }
82
83 pub fn as_object(&self) -> Option<&HashMap<String, ConfigValue>> {
85 match self {
86 ConfigValue::Object(obj) => Some(obj),
87 _ => None,
88 }
89 }
90
91 pub fn is_null(&self) -> bool {
93 matches!(self, ConfigValue::Null)
94 }
95}
96
97impl From<String> for ConfigValue {
98 fn from(s: String) -> Self {
99 ConfigValue::String(s)
100 }
101}
102
103impl From<&str> for ConfigValue {
104 fn from(s: &str) -> Self {
105 ConfigValue::String(s.to_string())
106 }
107}
108
109impl From<i64> for ConfigValue {
110 fn from(i: i64) -> Self {
111 ConfigValue::Integer(i)
112 }
113}
114
115impl From<f64> for ConfigValue {
116 fn from(f: f64) -> Self {
117 ConfigValue::Float(f)
118 }
119}
120
121impl From<bool> for ConfigValue {
122 fn from(b: bool) -> Self {
123 ConfigValue::Boolean(b)
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct Config {
130 data: HashMap<String, ConfigValue>,
131 sources: Vec<ConfigSource>,
132 metadata: ConfigMetadata,
133}
134
135#[derive(Debug, Clone)]
136pub struct ConfigMetadata {
137 pub loaded_at: SystemTime,
138 pub source_files: Vec<PathBuf>,
139 pub env_vars_used: Vec<String>,
140}
141
142#[derive(Debug, Clone)]
143pub enum ConfigSource {
144 File(PathBuf),
145 Environment,
146 CommandLine,
147 Default,
148}
149
150impl Default for Config {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl Config {
157 pub fn new() -> Self {
159 Self {
160 data: HashMap::new(),
161 sources: Vec::new(),
162 metadata: ConfigMetadata {
163 loaded_at: SystemTime::now(),
164 source_files: Vec::new(),
165 env_vars_used: Vec::new(),
166 },
167 }
168 }
169
170 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, UtilsError> {
172 let path = path.as_ref();
173 let content = fs::read_to_string(path).map_err(|e| {
174 UtilsError::InvalidParameter(format!("Failed to read config file: {e}"))
175 })?;
176
177 let mut config = Self::new();
178 config.sources.push(ConfigSource::File(path.to_path_buf()));
179 config.metadata.source_files.push(path.to_path_buf());
180
181 match path.extension().and_then(|ext| ext.to_str()) {
182 Some("json") => config.load_json(&content)?,
183 Some("toml") => config.load_toml(&content)?,
184 Some("yaml") | Some("yml") => config.load_yaml(&content)?,
185 _ => {
186 return Err(UtilsError::InvalidParameter(
187 "Unsupported config file format".to_string(),
188 ))
189 }
190 }
191
192 Ok(config)
193 }
194
195 fn load_json(&mut self, content: &str) -> Result<(), UtilsError> {
197 let json_value: serde_json::Value = serde_json::from_str(content)
198 .map_err(|e| UtilsError::InvalidParameter(format!("Invalid JSON: {e}")))?;
199
200 self.data = Self::json_to_config_map(json_value);
201 Ok(())
202 }
203
204 fn load_toml(&mut self, _content: &str) -> Result<(), UtilsError> {
206 Err(UtilsError::InvalidParameter(
208 "TOML support not implemented".to_string(),
209 ))
210 }
211
212 fn load_yaml(&mut self, _content: &str) -> Result<(), UtilsError> {
214 Err(UtilsError::InvalidParameter(
216 "YAML support not implemented".to_string(),
217 ))
218 }
219
220 fn json_to_config_map(json: serde_json::Value) -> HashMap<String, ConfigValue> {
222 match json {
223 serde_json::Value::Object(map) => map
224 .into_iter()
225 .map(|(k, v)| (k, Self::json_value_to_config_value(v)))
226 .collect(),
227 _ => HashMap::new(),
228 }
229 }
230
231 fn json_value_to_config_value(json: serde_json::Value) -> ConfigValue {
233 match json {
234 serde_json::Value::String(s) => ConfigValue::String(s),
235 serde_json::Value::Number(n) => {
236 if let Some(i) = n.as_i64() {
237 ConfigValue::Integer(i)
238 } else if let Some(f) = n.as_f64() {
239 ConfigValue::Float(f)
240 } else {
241 ConfigValue::Null
242 }
243 }
244 serde_json::Value::Bool(b) => ConfigValue::Boolean(b),
245 serde_json::Value::Array(arr) => ConfigValue::Array(
246 arr.into_iter()
247 .map(Self::json_value_to_config_value)
248 .collect(),
249 ),
250 serde_json::Value::Object(obj) => ConfigValue::Object(
251 obj.into_iter()
252 .map(|(k, v)| (k, Self::json_value_to_config_value(v)))
253 .collect(),
254 ),
255 serde_json::Value::Null => ConfigValue::Null,
256 }
257 }
258
259 pub fn load_environment(&mut self, prefix: &str) -> Result<(), UtilsError> {
261 self.sources.push(ConfigSource::Environment);
262
263 for (key, value) in env::vars() {
264 if let Some(stripped) = key.strip_prefix(prefix) {
265 let config_key = stripped.to_lowercase().replace('_', ".");
266 self.data
267 .insert(config_key, ConfigValue::String(value.clone()));
268 self.metadata.env_vars_used.push(key);
269 }
270 }
271
272 Ok(())
273 }
274
275 pub fn get(&self, key: &str) -> Option<&ConfigValue> {
277 if key.contains('.') {
278 self.get_nested(key)
279 } else {
280 self.data.get(key)
281 }
282 }
283
284 fn get_nested(&self, key: &str) -> Option<&ConfigValue> {
286 let parts: Vec<&str> = key.split('.').collect();
287 let mut current = self.data.get(parts[0])?;
288
289 for part in &parts[1..] {
290 if let ConfigValue::Object(obj) = current {
291 current = obj.get(*part)?;
292 } else {
293 return None;
294 }
295 }
296
297 Some(current)
298 }
299
300 pub fn set(&mut self, key: &str, value: ConfigValue) {
302 if key.contains('.') {
303 self.set_nested(key, value);
304 } else {
305 self.data.insert(key.to_string(), value);
306 }
307 }
308
309 fn set_nested(&mut self, key: &str, value: ConfigValue) {
311 let parts: Vec<&str> = key.split('.').collect();
312 if parts.is_empty() {
313 return;
314 }
315
316 if parts.len() == 1 {
317 self.data.insert(parts[0].to_string(), value);
318 return;
319 }
320
321 Self::set_nested_recursive(&mut self.data, &parts, 0, value);
323 }
324
325 fn set_nested_recursive(
327 current: &mut HashMap<String, ConfigValue>,
328 parts: &[&str],
329 index: usize,
330 value: ConfigValue,
331 ) {
332 if index == parts.len() - 1 {
333 current.insert(parts[index].to_string(), value);
335 return;
336 }
337
338 let part = parts[index].to_string();
339
340 let entry = current
342 .entry(part)
343 .or_insert_with(|| ConfigValue::Object(HashMap::new()));
344
345 match entry {
346 ConfigValue::Object(ref mut obj) => {
347 Self::set_nested_recursive(obj, parts, index + 1, value);
348 }
349 _ => {
350 *entry = ConfigValue::Object(HashMap::new());
352 if let ConfigValue::Object(ref mut obj) = entry {
353 Self::set_nested_recursive(obj, parts, index + 1, value);
354 }
355 }
356 }
357 }
358
359 pub fn get_string(&self, key: &str, default: &str) -> String {
361 self.get(key)
362 .and_then(|v| v.as_string())
363 .unwrap_or_else(|| default.to_string())
364 }
365
366 pub fn get_i64(&self, key: &str, default: i64) -> i64 {
368 self.get(key).and_then(|v| v.as_i64()).unwrap_or(default)
369 }
370
371 pub fn get_f64(&self, key: &str, default: f64) -> f64 {
373 self.get(key).and_then(|v| v.as_f64()).unwrap_or(default)
374 }
375
376 pub fn get_bool(&self, key: &str, default: bool) -> bool {
378 self.get(key).and_then(|v| v.as_bool()).unwrap_or(default)
379 }
380
381 pub fn merge(&mut self, other: &Config) {
383 for (key, value) in &other.data {
384 self.data.insert(key.clone(), value.clone());
385 }
386 self.sources.extend(other.sources.clone());
387 }
388
389 pub fn keys(&self) -> Vec<String> {
391 let mut keys = Vec::new();
392 self.collect_keys("", &self.data, &mut keys);
393 keys
394 }
395
396 #[allow(clippy::only_used_in_recursion)]
398 fn collect_keys(
399 &self,
400 prefix: &str,
401 data: &HashMap<String, ConfigValue>,
402 keys: &mut Vec<String>,
403 ) {
404 for (key, value) in data {
405 let full_key = if prefix.is_empty() {
406 key.clone()
407 } else {
408 format!("{prefix}.{key}")
409 };
410
411 keys.push(full_key.clone());
412
413 if let ConfigValue::Object(obj) = value {
414 self.collect_keys(&full_key, obj, keys);
415 }
416 }
417 }
418
419 pub fn to_json(&self) -> Result<String, UtilsError> {
421 let json_value = self.config_map_to_json(&self.data);
422 serde_json::to_string_pretty(&json_value)
423 .map_err(|e| UtilsError::InvalidParameter(format!("Failed to serialize config: {e}")))
424 }
425
426 fn config_map_to_json(&self, data: &HashMap<String, ConfigValue>) -> serde_json::Value {
428 let mut map = serde_json::Map::new();
429
430 for (key, value) in data {
431 map.insert(key.clone(), self.config_value_to_json(value));
432 }
433
434 serde_json::Value::Object(map)
435 }
436
437 #[allow(clippy::only_used_in_recursion)]
439 fn config_value_to_json(&self, value: &ConfigValue) -> serde_json::Value {
440 match value {
441 ConfigValue::String(s) => serde_json::Value::String(s.clone()),
442 ConfigValue::Integer(i) => serde_json::Value::Number((*i).into()),
443 ConfigValue::Float(f) => serde_json::Value::Number(
444 serde_json::Number::from_f64(*f).unwrap_or_else(|| 0.into()),
445 ),
446 ConfigValue::Boolean(b) => serde_json::Value::Bool(*b),
447 ConfigValue::Array(arr) => {
448 serde_json::Value::Array(arr.iter().map(|v| self.config_value_to_json(v)).collect())
449 }
450 ConfigValue::Object(obj) => {
451 let mut map = serde_json::Map::new();
452 for (k, v) in obj {
453 map.insert(k.clone(), self.config_value_to_json(v));
454 }
455 serde_json::Value::Object(map)
456 }
457 ConfigValue::Null => serde_json::Value::Null,
458 }
459 }
460}
461
462pub struct ConfigValidator;
464
465impl ConfigValidator {
466 pub fn validate_required_keys(
468 config: &Config,
469 required_keys: &[&str],
470 ) -> Result<(), UtilsError> {
471 for key in required_keys {
472 if config.get(key).is_none() {
473 return Err(UtilsError::InvalidParameter(format!(
474 "Required configuration key '{key}' is missing"
475 )));
476 }
477 }
478 Ok(())
479 }
480
481 pub fn validate_types(
483 config: &Config,
484 type_specs: &HashMap<&str, &str>,
485 ) -> Result<(), UtilsError> {
486 for (key, expected_type) in type_specs {
487 if let Some(value) = config.get(key) {
488 let valid = match *expected_type {
489 "string" => matches!(value, ConfigValue::String(_)),
490 "integer" => matches!(value, ConfigValue::Integer(_)),
491 "float" => {
492 matches!(value, ConfigValue::Float(_))
493 || matches!(value, ConfigValue::Integer(_))
494 }
495 "boolean" => matches!(value, ConfigValue::Boolean(_)),
496 "array" => matches!(value, ConfigValue::Array(_)),
497 "object" => matches!(value, ConfigValue::Object(_)),
498 _ => false,
499 };
500
501 if !valid {
502 return Err(UtilsError::InvalidParameter(format!(
503 "Configuration key '{key}' has wrong type, expected {expected_type}"
504 )));
505 }
506 }
507 }
508 Ok(())
509 }
510
511 pub fn validate_ranges(
513 config: &Config,
514 range_specs: &HashMap<&str, (f64, f64)>,
515 ) -> Result<(), UtilsError> {
516 for (key, (min_val, max_val)) in range_specs {
517 if let Some(value) = config.get(key) {
518 let numeric_value = match value {
519 ConfigValue::Integer(i) => Some(*i as f64),
520 ConfigValue::Float(f) => Some(*f),
521 _ => None,
522 };
523
524 if let Some(val) = numeric_value {
525 if val < *min_val || val > *max_val {
526 return Err(UtilsError::InvalidParameter(format!(
527 "Configuration key '{key}' value {val} is outside valid range [{min_val}, {max_val}]"
528 )));
529 }
530 }
531 }
532 }
533 Ok(())
534 }
535
536 pub fn validate_custom<F>(config: &Config, validator: F) -> Result<(), UtilsError>
538 where
539 F: Fn(&Config) -> Result<(), String>,
540 {
541 validator(config).map_err(UtilsError::InvalidParameter)
542 }
543}
544
545pub struct HotReloadConfig {
547 config: Arc<RwLock<Config>>,
548 file_path: PathBuf,
549 last_modified: SystemTime,
550 check_interval: Duration,
551}
552
553impl HotReloadConfig {
554 pub fn new<P: AsRef<Path>>(file_path: P, check_interval: Duration) -> Result<Self, UtilsError> {
556 let file_path = file_path.as_ref().to_path_buf();
557 let config = Config::from_file(&file_path)?;
558
559 let last_modified = fs::metadata(&file_path)
560 .and_then(|m| m.modified())
561 .map_err(|e| {
562 UtilsError::InvalidParameter(format!("Failed to get file metadata: {e}"))
563 })?;
564
565 Ok(Self {
566 config: Arc::new(RwLock::new(config)),
567 file_path,
568 last_modified,
569 check_interval,
570 })
571 }
572
573 pub fn get_config(&self) -> Arc<RwLock<Config>> {
575 self.config.clone()
576 }
577
578 pub fn check_and_reload(&mut self) -> Result<bool, UtilsError> {
580 let current_modified = fs::metadata(&self.file_path)
581 .and_then(|m| m.modified())
582 .map_err(|e| {
583 UtilsError::InvalidParameter(format!("Failed to get file metadata: {e}"))
584 })?;
585
586 if current_modified > self.last_modified {
587 let new_config = Config::from_file(&self.file_path)?;
588
589 {
590 let mut config = self.config.write().unwrap();
591 *config = new_config;
592 }
593
594 self.last_modified = current_modified;
595 Ok(true)
596 } else {
597 Ok(false)
598 }
599 }
600
601 pub fn start_auto_reload(mut self) -> std::thread::JoinHandle<()> {
603 std::thread::spawn(move || loop {
604 std::thread::sleep(self.check_interval);
605
606 if let Err(e) = self.check_and_reload() {
607 eprintln!("Error reloading config: {e}");
608 }
609 })
610 }
611}
612
613pub struct ArgParser {
615 args: Vec<String>,
616 config: Config,
617}
618
619impl Default for ArgParser {
620 fn default() -> Self {
621 Self::new()
622 }
623}
624
625impl ArgParser {
626 pub fn new() -> Self {
628 let args: Vec<String> = env::args().collect();
629 Self {
630 args,
631 config: Config::new(),
632 }
633 }
634
635 pub fn parse(&mut self) -> Result<(), UtilsError> {
637 let mut i = 1; while i < self.args.len() {
640 let arg = &self.args[i];
641
642 if let Some(stripped) = arg.strip_prefix("--") {
643 let key = stripped;
645
646 if let Some(eq_pos) = key.find('=') {
647 let (k, v) = key.split_at(eq_pos);
649 let value = &v[1..]; self.config.set(k, self.parse_value(value));
651 } else if i + 1 < self.args.len() && !self.args[i + 1].starts_with('-') {
652 i += 1;
654 let value = &self.args[i];
655 self.config.set(key, self.parse_value(value));
656 } else {
657 self.config.set(key, ConfigValue::Boolean(true));
659 }
660 } else if arg.starts_with('-') && arg.len() == 2 {
661 let key = &arg[1..];
663
664 if i + 1 < self.args.len() && !self.args[i + 1].starts_with('-') {
665 i += 1;
666 let value = &self.args[i];
667 self.config.set(key, self.parse_value(value));
668 } else {
669 self.config.set(key, ConfigValue::Boolean(true));
671 }
672 }
673
674 i += 1;
675 }
676
677 Ok(())
678 }
679
680 #[allow(clippy::only_used_in_recursion)]
682 fn parse_value(&self, value: &str) -> ConfigValue {
683 if let Ok(b) = value.parse::<bool>() {
685 ConfigValue::Boolean(b)
686 } else if let Ok(i) = value.parse::<i64>() {
687 ConfigValue::Integer(i)
688 } else if let Ok(f) = value.parse::<f64>() {
689 ConfigValue::Float(f)
690 } else if value.starts_with('[') && value.ends_with(']') {
691 let inner = &value[1..value.len() - 1];
693 let items: Vec<ConfigValue> = inner
694 .split(',')
695 .map(|s| self.parse_value(s.trim()))
696 .collect();
697 ConfigValue::Array(items)
698 } else {
699 ConfigValue::String(value.to_string())
700 }
701 }
702
703 pub fn get_config(self) -> Config {
705 self.config
706 }
707}
708
709pub struct ConfigBuilder {
711 config: Config,
712}
713
714impl Default for ConfigBuilder {
715 fn default() -> Self {
716 Self::new()
717 }
718}
719
720impl ConfigBuilder {
721 pub fn new() -> Self {
723 Self {
724 config: Config::new(),
725 }
726 }
727
728 pub fn add_file<P: AsRef<Path>>(mut self, path: P) -> Result<Self, UtilsError> {
730 let file_config = Config::from_file(path)?;
731 self.config.merge(&file_config);
732 Ok(self)
733 }
734
735 pub fn add_env(mut self, prefix: &str) -> Result<Self, UtilsError> {
737 self.config.load_environment(prefix)?;
738 Ok(self)
739 }
740
741 pub fn add_args(mut self) -> Result<Self, UtilsError> {
743 let mut parser = ArgParser::new();
744 parser.parse()?;
745 self.config.merge(&parser.get_config());
746 Ok(self)
747 }
748
749 pub fn set_default(mut self, key: &str, value: ConfigValue) -> Self {
751 if self.config.get(key).is_none() {
752 self.config.set(key, value);
753 }
754 self
755 }
756
757 pub fn validate<F>(self, validator: F) -> Result<Self, UtilsError>
759 where
760 F: Fn(&Config) -> Result<(), String>,
761 {
762 ConfigValidator::validate_custom(&self.config, validator)?;
763 Ok(self)
764 }
765
766 pub fn build(self) -> Config {
768 self.config
769 }
770}
771
772#[allow(non_snake_case)]
773#[cfg(test)]
774mod tests {
775 use super::*;
776 use std::io::Write;
777 use tempfile::NamedTempFile;
778
779 #[test]
780 fn test_config_value_conversions() {
781 let string_val = ConfigValue::String("test".to_string());
782 assert_eq!(string_val.as_string(), Some("test".to_string()));
783
784 let int_val = ConfigValue::Integer(42);
785 assert_eq!(int_val.as_i64(), Some(42));
786 assert_eq!(int_val.as_f64(), Some(42.0));
787
788 let bool_val = ConfigValue::Boolean(true);
789 assert_eq!(bool_val.as_bool(), Some(true));
790
791 let float_val = ConfigValue::Float(std::f64::consts::PI);
792 assert_eq!(float_val.as_f64(), Some(std::f64::consts::PI));
793 }
794
795 #[test]
796 fn test_config_get_set() {
797 let mut config = Config::new();
798
799 config.set("test.key", ConfigValue::String("value".to_string()));
800 assert_eq!(config.get_string("test.key", "default"), "value");
801
802 config.set("number", ConfigValue::Integer(42));
803 assert_eq!(config.get_i64("number", 0), 42);
804
805 config.set("flag", ConfigValue::Boolean(true));
806 assert!(config.get_bool("flag", false));
807 }
808
809 #[test]
810 fn test_json_config_loading() {
811 let json_content = r#"
812 {
813 "database": {
814 "host": "localhost",
815 "port": 5432,
816 "name": "test_db"
817 },
818 "debug": true,
819 "timeout": 30.5
820 }
821 "#;
822
823 let mut temp_file = NamedTempFile::with_suffix(".json").unwrap();
824 temp_file.write_all(json_content.as_bytes()).unwrap();
825
826 let config = Config::from_file(temp_file.path()).unwrap();
827
828 assert_eq!(config.get_string("database.host", ""), "localhost");
829 assert_eq!(config.get_i64("database.port", 0), 5432);
830 assert_eq!(config.get_string("database.name", ""), "test_db");
831 assert!(config.get_bool("debug", false));
832 assert_eq!(config.get_f64("timeout", 0.0), 30.5);
833 }
834
835 #[test]
836 fn test_config_validation() {
837 let mut config = Config::new();
838 config.set("required_key", ConfigValue::String("value".to_string()));
839 config.set("number_key", ConfigValue::Integer(50));
840
841 let required_keys = vec!["required_key"];
843 assert!(ConfigValidator::validate_required_keys(&config, &required_keys).is_ok());
844
845 let missing_keys = vec!["missing_key"];
846 assert!(ConfigValidator::validate_required_keys(&config, &missing_keys).is_err());
847
848 let mut type_specs = HashMap::new();
850 type_specs.insert("required_key", "string");
851 type_specs.insert("number_key", "integer");
852 assert!(ConfigValidator::validate_types(&config, &type_specs).is_ok());
853
854 type_specs.insert("number_key", "string");
855 assert!(ConfigValidator::validate_types(&config, &type_specs).is_err());
856
857 let mut range_specs = HashMap::new();
859 range_specs.insert("number_key", (0.0, 100.0));
860 assert!(ConfigValidator::validate_ranges(&config, &range_specs).is_ok());
861
862 range_specs.insert("number_key", (60.0, 100.0));
863 assert!(ConfigValidator::validate_ranges(&config, &range_specs).is_err());
864 }
865
866 #[test]
867 fn test_arg_parser() {
868 let args = vec![
870 "program".to_string(),
871 "--host=localhost".to_string(),
872 "--port".to_string(),
873 "8080".to_string(),
874 "--debug".to_string(),
875 "-v".to_string(),
876 ];
877
878 let mut parser = ArgParser {
879 args,
880 config: Config::new(),
881 };
882 parser.parse().unwrap();
883
884 let config = parser.get_config();
885 assert_eq!(config.get_string("host", ""), "localhost");
886 assert_eq!(config.get_i64("port", 0), 8080);
887 assert!(config.get_bool("debug", false));
888 assert!(config.get_bool("v", false));
889 }
890
891 #[test]
892 fn test_config_builder() {
893 let config = ConfigBuilder::new()
894 .set_default("host", ConfigValue::String("localhost".to_string()))
895 .set_default("port", ConfigValue::Integer(8080))
896 .set_default("debug", ConfigValue::Boolean(false))
897 .build();
898
899 assert_eq!(config.get_string("host", ""), "localhost");
900 assert_eq!(config.get_i64("port", 0), 8080);
901 assert!(!config.get_bool("debug", true));
902 }
903
904 #[test]
905 fn test_config_merge() {
906 let mut config1 = Config::new();
907 config1.set("key1", ConfigValue::String("value1".to_string()));
908 config1.set("key2", ConfigValue::Integer(42));
909
910 let mut config2 = Config::new();
911 config2.set("key2", ConfigValue::Integer(100)); config2.set("key3", ConfigValue::Boolean(true)); config1.merge(&config2);
915
916 assert_eq!(config1.get_string("key1", ""), "value1");
917 assert_eq!(config1.get_i64("key2", 0), 100); assert!(config1.get_bool("key3", false)); }
920}