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}