Skip to main content

qubit_config/
config.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # Configuration Manager
10//!
11//! Provides storage, retrieval, and management of configurations.
12//!
13//! # Author
14//!
15//! Haixing Hu
16
17#![allow(private_bounds)]
18
19use std::collections::HashMap;
20
21use serde::{Deserialize, Serialize};
22
23use super::{utils, ConfigError, ConfigResult, Property};
24use qubit_value::multi_values::{
25    MultiValuesAddArg, MultiValuesAdder, MultiValuesFirstGetter, MultiValuesGetter,
26    MultiValuesMultiAdder, MultiValuesSetArg, MultiValuesSetter, MultiValuesSetterSlice,
27    MultiValuesSingleSetter,
28};
29use qubit_value::MultiValues;
30
31/// Default maximum depth for variable substitution
32pub const DEFAULT_MAX_SUBSTITUTION_DEPTH: usize = 64;
33
34/// Configuration Manager
35///
36/// Manages a set of configuration properties with type-safe read/write interfaces.
37///
38/// # Features
39///
40/// - Supports multiple data types
41/// - Supports variable substitution (`${var_name}` format)
42/// - Supports configuration merging
43/// - Supports final value protection
44/// - Thread-safe (when wrapped in `Arc<RwLock<Config>>`)
45///
46/// # Important Limitations of Generic set/add Methods
47///
48/// **`u8` type does not support generic `set()` and `add()` methods**. See `MultiValues` documentation for details.
49///
50/// For `u8` type configuration values, use dedicated methods:
51///
52/// ```rust,ignore
53/// use qubit_config::Config;
54///
55/// let mut config = Config::new();
56///
57/// // ❌ Not supported: config.set("byte_value", 42u8)?;
58///
59/// // ✅ Method 1: Use dedicated method via get_property_mut
60/// config.get_property_mut("byte_value")
61///     .unwrap()
62///     .set_uint8(42)
63///     .unwrap();
64///
65/// // ✅ Method 2: Create property first if it doesn't exist
66/// if config.get_property("byte_value").is_none() {
67///     let mut prop = Property::new("byte_value");
68///     prop.set_uint8(42).unwrap();
69///     config.properties.insert("byte_value".to_string(), prop);
70/// }
71///
72/// // Reading works normally
73/// let value: u8 = config.get("byte_value")?;
74/// ```
75///
76/// **Recommendation**: If you truly need to store `u8` values, consider using `u16` instead,
77/// as `u8` is rarely used for configuration values in practice, while `Vec<u8>` is more
78/// commonly used for byte arrays (such as keys, hashes, etc.).
79///
80/// # Examples
81///
82/// ```rust,ignore
83/// use qubit_config::Config;
84///
85/// let mut config = Config::new();
86///
87/// // Set configuration values (type inference)
88/// config.set("port", 8080)?;                    // inferred as i32
89/// config.set("host", "localhost")?;              // &str automatically converted to String
90/// config.set("debug", true)?;                   // inferred as bool
91/// config.set("timeout", 30.5)?;                 // inferred as f64
92///
93/// // Set multiple values (type inference)
94/// config.set("ports", vec![8080, 8081, 8082])?; // inferred as i32
95/// config.set("hosts", &["host1", "host2"])?;     // &str automatically converted
96///
97/// // Read configuration values (type inference)
98/// let port: i32 = config.get("port")?;
99/// let host: String = config.get("host")?;
100/// let debug: bool = config.get("debug")?;
101///
102/// // Read configuration values (turbofish)
103/// let port = config.get::<i32>("port")?;
104///
105/// // Read configuration value or use default
106/// let timeout: u64 = config.get_or("timeout", 30);
107/// ```
108///
109/// # Author
110///
111/// Haixing Hu
112///
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub struct Config {
115    /// Configuration description
116    description: Option<String>,
117    /// Configuration property mapping
118    properties: HashMap<String, Property>,
119    /// Whether variable substitution is enabled
120    enable_variable_substitution: bool,
121    /// Maximum depth for variable substitution
122    max_substitution_depth: usize,
123}
124
125impl Config {
126    /// Creates a new empty configuration
127    ///
128    /// # Returns
129    ///
130    /// Returns a new configuration instance
131    ///
132    /// # Examples
133    ///
134    /// ```rust,ignore
135    /// use qubit_config::Config;
136    ///
137    /// let config = Config::new();
138    /// assert!(config.is_empty());
139    /// ```
140    pub fn new() -> Self {
141        Self {
142            description: None,
143            properties: HashMap::new(),
144            enable_variable_substitution: true,
145            max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
146        }
147    }
148
149    /// Creates a configuration with description
150    ///
151    /// # Parameters
152    ///
153    /// * `description` - Configuration description
154    ///
155    /// # Returns
156    ///
157    /// Returns a new configuration instance
158    ///
159    /// # Examples
160    ///
161    /// ```rust,ignore
162    /// use qubit_config::Config;
163    ///
164    /// let config = Config::with_description("Server Configuration");
165    /// assert_eq!(config.description(), Some("Server Configuration"));
166    /// ```
167    pub fn with_description(description: &str) -> Self {
168        Self {
169            description: Some(description.to_string()),
170            properties: HashMap::new(),
171            enable_variable_substitution: true,
172            max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
173        }
174    }
175
176    // ========================================================================
177    // Basic Property Access
178    // ========================================================================
179
180    /// Gets the configuration description
181    ///
182    /// # Returns
183    ///
184    /// Returns the configuration description as Option
185    pub fn description(&self) -> Option<&str> {
186        self.description.as_deref()
187    }
188
189    /// Sets the configuration description
190    ///
191    /// # Parameters
192    ///
193    /// * `description` - Configuration description
194    pub fn set_description(&mut self, description: Option<String>) {
195        self.description = description;
196    }
197
198    /// Checks if variable substitution is enabled
199    ///
200    /// # Returns
201    ///
202    /// Returns `true` if variable substitution is enabled
203    pub fn is_enable_variable_substitution(&self) -> bool {
204        self.enable_variable_substitution
205    }
206
207    /// Sets whether to enable variable substitution
208    ///
209    /// # Parameters
210    ///
211    /// * `enable` - Whether to enable
212    pub fn set_enable_variable_substitution(&mut self, enable: bool) {
213        self.enable_variable_substitution = enable;
214    }
215
216    /// Gets the maximum depth for variable substitution
217    ///
218    /// # Returns
219    ///
220    /// Returns the maximum depth value
221    pub fn max_substitution_depth(&self) -> usize {
222        self.max_substitution_depth
223    }
224
225    /// Sets the maximum depth for variable substitution
226    ///
227    /// # Parameters
228    ///
229    /// * `depth` - Maximum depth
230    pub fn set_max_substitution_depth(&mut self, depth: usize) {
231        self.max_substitution_depth = depth;
232    }
233
234    // ========================================================================
235    // Configuration Item Management
236    // ========================================================================
237
238    /// Checks if the configuration contains an item with the specified name
239    ///
240    /// # Parameters
241    ///
242    /// * `name` - Configuration item name
243    ///
244    /// # Returns
245    ///
246    /// Returns `true` if the configuration item exists
247    ///
248    /// # Examples
249    ///
250    /// ```rust,ignore
251    /// use qubit_config::Config;
252    ///
253    /// let mut config = Config::new();
254    /// config.set("port", 8080)?;
255    ///
256    /// assert!(config.contains("port"));
257    /// assert!(!config.contains("host"));
258    /// ```
259    pub fn contains(&self, name: &str) -> bool {
260        self.properties.contains_key(name)
261    }
262
263    /// Gets a reference to a configuration item
264    ///
265    /// # Parameters
266    ///
267    /// * `name` - Configuration item name
268    ///
269    /// # Returns
270    ///
271    /// Returns Option containing the configuration item
272    pub fn get_property(&self, name: &str) -> Option<&Property> {
273        self.properties.get(name)
274    }
275
276    /// Gets a mutable reference to a configuration item
277    ///
278    /// # Parameters
279    ///
280    /// * `name` - Configuration item name
281    ///
282    /// # Returns
283    ///
284    /// Returns mutable Option containing the configuration item
285    pub fn get_property_mut(&mut self, name: &str) -> Option<&mut Property> {
286        self.properties.get_mut(name)
287    }
288
289    /// Removes a configuration item
290    ///
291    /// # Parameters
292    ///
293    /// * `name` - Configuration item name
294    ///
295    /// # Returns
296    ///
297    /// Returns the removed configuration item, or None if it doesn't exist
298    ///
299    /// # Examples
300    ///
301    /// ```rust,ignore
302    /// use qubit_config::Config;
303    ///
304    /// let mut config = Config::new();
305    /// config.set("port", 8080)?;
306    ///
307    /// let removed = config.remove("port");
308    /// assert!(removed.is_some());
309    /// assert!(!config.contains("port"));
310    /// ```
311    pub fn remove(&mut self, name: &str) -> Option<Property> {
312        self.properties.remove(name)
313    }
314
315    /// Clears all configuration items
316    ///
317    /// # Examples
318    ///
319    /// ```rust,ignore
320    /// use qubit_config::Config;
321    ///
322    /// let mut config = Config::new();
323    /// config.set("port", 8080)?;
324    /// config.set("host", "localhost")?;
325    ///
326    /// config.clear();
327    /// assert!(config.is_empty());
328    /// ```
329    pub fn clear(&mut self) {
330        self.properties.clear();
331    }
332
333    /// Gets the number of configuration items
334    ///
335    /// # Returns
336    ///
337    /// Returns the number of configuration items
338    pub fn len(&self) -> usize {
339        self.properties.len()
340    }
341
342    /// Checks if the configuration is empty
343    ///
344    /// # Returns
345    ///
346    /// Returns `true` if the configuration contains no items
347    pub fn is_empty(&self) -> bool {
348        self.properties.is_empty()
349    }
350
351    /// Gets all configuration item names
352    ///
353    /// # Returns
354    ///
355    /// Returns a Vec of configuration item names
356    ///
357    /// # Examples
358    ///
359    /// ```rust,ignore
360    /// use qubit_config::Config;
361    ///
362    /// let mut config = Config::new();
363    /// config.set("port", 8080)?;
364    /// config.set("host", "localhost")?;
365    ///
366    /// let keys = config.keys();
367    /// assert_eq!(keys.len(), 2);
368    /// assert!(keys.contains(&"port".to_string()));
369    /// assert!(keys.contains(&"host".to_string()));
370    /// ```
371    pub fn keys(&self) -> Vec<String> {
372        self.properties.keys().cloned().collect()
373    }
374
375    // ========================================================================
376    // Core Generic Methods
377    // ========================================================================
378
379    /// Gets a configuration value
380    ///
381    /// This is the core method for getting configuration values, supporting type inference.
382    ///
383    /// # Note
384    ///
385    /// This method does not perform variable substitution for string types. If you need
386    /// variable substitution, please use the `get_string` method.
387    ///
388    /// # Type Parameters
389    ///
390    /// * `T` - Target type, must implement `FromPropertyValue` trait
391    ///
392    /// # Parameters
393    ///
394    /// * `name` - Configuration item name
395    ///
396    /// # Returns
397    ///
398    /// Returns the value of the specified type on success, or an error on failure
399    ///
400    /// # Errors
401    ///
402    /// - Returns `ConfigError::PropertyNotFound` if the configuration item doesn't exist
403    /// - Returns `ConfigError::PropertyHasNoValue` if the configuration item has no value
404    /// - Returns `ConfigError::TypeMismatch` if the type doesn't match
405    ///
406    /// # Examples
407    ///
408    /// ```rust,ignore
409    /// use qubit_config::Config;
410    ///
411    /// let mut config = Config::new();
412    /// config.set("port", 8080)?;
413    /// config.set("host", "localhost")?;
414    ///
415    /// // Method 1: Type inference
416    /// let port: i32 = config.get("port")?;
417    /// let host: String = config.get("host")?;
418    ///
419    /// // Method 2: Turbofish
420    /// let port = config.get::<i32>("port")?;
421    /// let host = config.get::<String>("host")?;
422    ///
423    /// // Method 3: Inference from usage
424    /// fn start_server(port: i32, host: String) { }
425    /// start_server(config.get("port")?, config.get("host")?);
426    /// ```
427    pub fn get<T>(&self, name: &str) -> ConfigResult<T>
428    where
429        MultiValues: MultiValuesFirstGetter<T>,
430    {
431        let property = self
432            .properties
433            .get(name)
434            .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
435
436        property.get_first::<T>().map_err(ConfigError::from)
437    }
438
439    /// Gets a configuration value or returns a default value
440    ///
441    /// Returns the default value if the configuration item doesn't exist or retrieval fails.
442    ///
443    /// # Type Parameters
444    ///
445    /// * `T` - Target type, must implement `FromPropertyValue` trait
446    ///
447    /// # Parameters
448    ///
449    /// * `name` - Configuration item name
450    /// * `default` - Default value
451    ///
452    /// # Returns
453    ///
454    /// Returns the configuration value or default value
455    ///
456    /// # Examples
457    ///
458    /// ```rust,ignore
459    /// use qubit_config::Config;
460    ///
461    /// let config = Config::new();
462    ///
463    /// let port: i32 = config.get_or("port", 8080);
464    /// let host: String = config.get_or("host", "localhost".to_string());
465    ///
466    /// assert_eq!(port, 8080);
467    /// assert_eq!(host, "localhost");
468    /// ```
469    pub fn get_or<T>(&self, name: &str, default: T) -> T
470    where
471        MultiValues: MultiValuesFirstGetter<T>,
472    {
473        self.get(name).unwrap_or(default)
474    }
475
476    /// Gets a list of configuration values
477    ///
478    /// Gets all values of a configuration item (multi-value configuration).
479    ///
480    /// # Type Parameters
481    ///
482    /// * `T` - Target type, must implement `FromPropertyValue` trait
483    ///
484    /// # Parameters
485    ///
486    /// * `name` - Configuration item name
487    ///
488    /// # Returns
489    ///
490    /// Returns a list of values on success, or an error on failure
491    ///
492    /// # Examples
493    ///
494    /// ```rust,ignore
495    /// use qubit_config::Config;
496    ///
497    /// let mut config = Config::new();
498    /// config.set("ports", vec![8080, 8081, 8082])?;
499    ///
500    /// let ports: Vec<i32> = config.get_list("ports")?;
501    /// assert_eq!(ports, vec![8080, 8081, 8082]);
502    /// ```
503    pub fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
504    where
505        MultiValues: MultiValuesGetter<T>,
506    {
507        let property = self
508            .properties
509            .get(name)
510            .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
511
512        property.get::<T>().map_err(ConfigError::from)
513    }
514
515    /// Sets a configuration value
516    ///
517    /// This is the core method for setting configuration values, supporting type inference.
518    ///
519    /// # Type Parameters
520    ///
521    /// * `T` - Element type, automatically inferred from the `values` parameter
522    ///
523    /// # Parameters
524    ///
525    /// * `name` - Configuration item name
526    /// * `values` - Configuration value, supports `T`, `Vec<T>`, `&[T]`, and other types
527    ///
528    /// # Returns
529    ///
530    /// Returns Ok(()) on success, or an error on failure
531    ///
532    /// # Errors
533    ///
534    /// - Returns `ConfigError::PropertyIsFinal` if the configuration item is final
535    ///
536    /// # Examples
537    ///
538    /// ```rust,ignore
539    /// use qubit_config::Config;
540    ///
541    /// let mut config = Config::new();
542    ///
543    /// // Set single values (type auto-inference)
544    /// config.set("port", 8080)?;                    // T inferred as i32
545    /// config.set("host", "localhost")?;              // T inferred as String (&str auto-converted)
546    /// config.set("debug", true)?;                   // T inferred as bool
547    /// config.set("timeout", 30.5)?;                 // T inferred as f64
548    ///
549    /// // Set multiple values (type auto-inference)
550    /// config.set("ports", vec![8080, 8081, 8082])?; // T inferred as i32
551    /// config.set("hosts", &["host1", "host2"])?;     // T inferred as &str (auto-converted)
552    /// ```
553    pub fn set<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
554    where
555        S: for<'a> MultiValuesSetArg<'a>,
556        <S as MultiValuesSetArg<'static>>::Item: Clone,
557        MultiValues: MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
558            + MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
559            + MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
560    {
561        // Check if it's a final value
562        if let Some(prop) = self.properties.get(name) {
563            if prop.is_final() {
564                return Err(ConfigError::PropertyIsFinal(name.to_string()));
565            }
566        }
567        let property = self
568            .properties
569            .entry(name.to_string())
570            .or_insert_with(|| Property::new(name));
571
572        property.set(values).map_err(ConfigError::from)
573    }
574
575    /// Adds configuration values
576    ///
577    /// Adds values to an existing configuration item (for multi-value configuration).
578    ///
579    /// # Type Parameters
580    ///
581    /// * `T` - Element type, automatically inferred from the `values` parameter
582    ///
583    /// # Parameters
584    ///
585    /// * `name` - Configuration item name
586    /// * `values` - Values to add, supports `T`, `Vec<T>`, `&[T]`, and other types
587    ///
588    /// # Returns
589    ///
590    /// Returns Ok(()) on success, or an error on failure
591    ///
592    /// # Examples
593    ///
594    /// ```rust,ignore
595    /// use qubit_config::Config;
596    ///
597    /// let mut config = Config::new();
598    /// config.set("port", 8080)?;                    // Set initial value
599    /// config.add("port", 8081)?;                    // Add single value
600    /// config.add("port", vec![8082, 8083])?;        // Add multiple values
601    /// config.add("port", &[8084, 8085])?;          // Add slice
602    ///
603    /// let ports: Vec<i32> = config.get_list("port")?;
604    /// assert_eq!(ports, vec![8080, 8081, 8082, 8083, 8084, 8085]);
605    /// ```
606    pub fn add<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
607    where
608        S: for<'a> MultiValuesAddArg<'a, Item = <S as MultiValuesSetArg<'static>>::Item>
609            + for<'a> MultiValuesSetArg<'a>,
610        <S as MultiValuesSetArg<'static>>::Item: Clone,
611        MultiValues: MultiValuesAdder<<S as MultiValuesSetArg<'static>>::Item>
612            + MultiValuesMultiAdder<<S as MultiValuesSetArg<'static>>::Item>
613            + MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
614            + MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
615            + MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
616    {
617        // Check if it's a final value
618        if let Some(prop) = self.properties.get(name) {
619            if prop.is_final() {
620                return Err(ConfigError::PropertyIsFinal(name.to_string()));
621            }
622        }
623
624        if let Some(property) = self.properties.get_mut(name) {
625            property.add(values).map_err(ConfigError::from)
626        } else {
627            let mut property = Property::new(name);
628            // Note: property.set() always returns Ok(()) in current MultiValues implementation,
629            // as it unconditionally replaces the entire value without any validation.
630            // We explicitly ignore the result to improve code coverage and avoid unreachable error paths.
631            let _ = property.set(values);
632            self.properties.insert(name.to_string(), property);
633            Ok(())
634        }
635    }
636
637    // ========================================================================
638    // String Special Handling (Variable Substitution)
639    // ========================================================================
640
641    /// Gets a string configuration value (with variable substitution)
642    ///
643    /// If variable substitution is enabled, automatically replaces variables in `${var_name}` format.
644    ///
645    /// # Parameters
646    ///
647    /// * `name` - Configuration item name
648    ///
649    /// # Returns
650    ///
651    /// Returns the string value on success, or an error on failure
652    ///
653    /// # Examples
654    ///
655    /// ```rust,ignore
656    /// use qubit_config::Config;
657    ///
658    /// let mut config = Config::new();
659    /// config.set("base_url", "http://localhost")?;
660    /// config.set("api_url", "${base_url}/api")?;
661    ///
662    /// let api_url = config.get_string("api_url")?;
663    /// assert_eq!(api_url, "http://localhost/api");
664    /// ```
665    pub fn get_string(&self, name: &str) -> ConfigResult<String> {
666        let value: String = self.get(name)?;
667        if self.enable_variable_substitution {
668            utils::substitute_variables(&value, self, self.max_substitution_depth)
669        } else {
670            Ok(value)
671        }
672    }
673
674    /// Gets a string configuration value or returns a default value (with variable substitution)
675    ///
676    /// # Parameters
677    ///
678    /// * `name` - Configuration item name
679    /// * `default` - Default value
680    ///
681    /// # Returns
682    ///
683    /// Returns the string value or default value
684    ///
685    pub fn get_string_or(&self, name: &str, default: &str) -> String {
686        self.get_string(name)
687            .unwrap_or_else(|_| default.to_string())
688    }
689
690    /// Gets a list of string configuration values (with variable substitution)
691    ///
692    /// If variable substitution is enabled, automatically replaces variables in `${var_name}` format for each string in the list.
693    ///
694    /// # Parameters
695    ///
696    /// * `name` - Configuration item name
697    ///
698    /// # Returns
699    ///
700    /// Returns a list of strings on success, or an error on failure
701    ///
702    /// # Examples
703    ///
704    /// ```rust,ignore
705    /// use qubit_config::Config;
706    ///
707    /// let mut config = Config::new();
708    /// config.set("base_path", "/opt/app")?;
709    /// config.set("paths", vec!["${base_path}/bin", "${base_path}/lib"])?;
710    ///
711    /// let paths = config.get_string_list("paths")?;
712    /// assert_eq!(paths, vec!["/opt/app/bin", "/opt/app/lib"]);
713    /// ```
714    pub fn get_string_list(&self, name: &str) -> ConfigResult<Vec<String>> {
715        let values: Vec<String> = self.get_list(name)?;
716        if self.enable_variable_substitution {
717            values
718                .into_iter()
719                .map(|v| utils::substitute_variables(&v, self, self.max_substitution_depth))
720                .collect()
721        } else {
722            Ok(values)
723        }
724    }
725
726    /// Gets a list of string configuration values or returns a default value (with variable substitution)
727    ///
728    /// # Parameters
729    ///
730    /// * `name` - Configuration item name
731    /// * `default` - Default value (can be array slice or vec)
732    ///
733    /// # Returns
734    ///
735    /// Returns the list of strings or default value
736    ///
737    /// # Examples
738    ///
739    /// ```rust,ignore
740    /// use qubit_config::Config;
741    ///
742    /// let config = Config::new();
743    ///
744    /// // Using array slice
745    /// let paths = config.get_string_list_or("paths", &["/default/path"]);
746    /// assert_eq!(paths, vec!["/default/path"]);
747    ///
748    /// // Using vec
749    /// let paths = config.get_string_list_or("paths", &vec!["path1", "path2"]);
750    /// assert_eq!(paths, vec!["path1", "path2"]);
751    /// ```
752    pub fn get_string_list_or(&self, name: &str, default: &[&str]) -> Vec<String> {
753        self.get_string_list(name)
754            .unwrap_or_else(|_| default.iter().map(|s| s.to_string()).collect())
755    }
756}
757
758impl Default for Config {
759    /// Creates a new default configuration
760    ///
761    /// # Returns
762    ///
763    /// Returns a new configuration instance
764    ///
765    /// # Examples
766    ///
767    /// ```rust,ignore
768    /// use qubit_config::Config;
769    ///
770    /// let config = Config::default();
771    /// assert!(config.is_empty());
772    /// ```
773    fn default() -> Self {
774        Self::new()
775    }
776}