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::de::DeserializeOwned;
22use serde::{Deserialize, Serialize};
23use serde_json::{Map, Value, from_value};
24
25use crate::config_prefix_view::ConfigPrefixView;
26use crate::config_reader::ConfigReader;
27use crate::constants::DEFAULT_MAX_SUBSTITUTION_DEPTH;
28use crate::source::ConfigSource;
29use crate::utils;
30use crate::{ConfigError, ConfigResult, Property};
31use qubit_common::DataType;
32use qubit_value::MultiValues;
33use qubit_value::multi_values::{
34 MultiValuesAddArg, MultiValuesAdder, MultiValuesFirstGetter, MultiValuesGetter,
35 MultiValuesMultiAdder, MultiValuesSetArg, MultiValuesSetter, MultiValuesSetterSlice,
36 MultiValuesSingleSetter,
37};
38
39/// Configuration Manager
40///
41/// Manages a set of configuration properties with type-safe read/write
42/// interfaces.
43///
44/// # Features
45///
46/// - Supports multiple data types
47/// - Supports variable substitution (`${var_name}` format)
48/// - Supports configuration merging
49/// - Supports final value protection
50/// - Thread-safe (when wrapped in `Arc<RwLock<Config>>`)
51///
52/// # Examples
53///
54/// ```rust
55/// use qubit_config::Config;
56///
57/// let mut config = Config::new();
58///
59/// // Set configuration values (type inference)
60/// config.set("port", 8080).unwrap(); // inferred as i32
61/// config.set("host", "localhost").unwrap();
62/// // &str is converted to String
63/// config.set("debug", true).unwrap(); // inferred as bool
64/// config.set("timeout", 30.5).unwrap(); // inferred as f64
65/// config.set("code", 42u8).unwrap(); // inferred as u8
66///
67/// // Set multiple values (type inference)
68/// config.set("ports", vec![8080, 8081, 8082]).unwrap(); // inferred as i32
69/// config.set("hosts", vec!["host1", "host2"]).unwrap();
70/// // &str elements are converted
71///
72/// // Read configuration values (type inference)
73/// let port: i32 = config.get("port").unwrap();
74/// let host: String = config.get("host").unwrap();
75/// let debug: bool = config.get("debug").unwrap();
76/// let code: u8 = config.get("code").unwrap();
77///
78/// // Read configuration values (turbofish)
79/// let port = config.get::<i32>("port").unwrap();
80///
81/// // Read configuration value or use default
82/// let timeout: u64 = config.get_or("timeout", 30);
83/// ```
84///
85/// # Author
86///
87/// Haixing Hu
88///
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct Config {
91 /// Configuration description
92 description: Option<String>,
93 /// Configuration property mapping
94 pub(crate) properties: HashMap<String, Property>,
95 /// Whether variable substitution is enabled
96 enable_variable_substitution: bool,
97 /// Maximum depth for variable substitution
98 max_substitution_depth: usize,
99}
100
101impl Config {
102 /// Creates a new empty configuration
103 ///
104 /// # Returns
105 ///
106 /// Returns a new configuration instance
107 ///
108 /// # Examples
109 ///
110 /// ```rust
111 /// use qubit_config::Config;
112 ///
113 /// let mut config = Config::new();
114 /// assert!(config.is_empty());
115 /// ```
116 #[inline]
117 pub fn new() -> Self {
118 Self {
119 description: None,
120 properties: HashMap::new(),
121 enable_variable_substitution: true,
122 max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
123 }
124 }
125
126 /// Creates a configuration with description
127 ///
128 /// # Parameters
129 ///
130 /// * `description` - Configuration description
131 ///
132 /// # Returns
133 ///
134 /// Returns a new configuration instance
135 ///
136 /// # Examples
137 ///
138 /// ```rust
139 /// use qubit_config::Config;
140 ///
141 /// let config = Config::with_description("Server Configuration");
142 /// assert_eq!(config.description(), Some("Server Configuration"));
143 /// ```
144 #[inline]
145 pub fn with_description(description: &str) -> Self {
146 Self {
147 description: Some(description.to_string()),
148 properties: HashMap::new(),
149 enable_variable_substitution: true,
150 max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
151 }
152 }
153
154 // ========================================================================
155 // Basic Property Access
156 // ========================================================================
157
158 /// Gets the configuration description
159 ///
160 /// # Returns
161 ///
162 /// Returns the configuration description as Option
163 #[inline]
164 pub fn description(&self) -> Option<&str> {
165 self.description.as_deref()
166 }
167
168 /// Sets the configuration description
169 ///
170 /// # Parameters
171 ///
172 /// * `description` - Configuration description
173 ///
174 /// # Returns
175 ///
176 /// Nothing.
177 #[inline]
178 pub fn set_description(&mut self, description: Option<String>) {
179 self.description = description;
180 }
181
182 /// Checks if variable substitution is enabled
183 ///
184 /// # Returns
185 ///
186 /// Returns `true` if variable substitution is enabled
187 #[inline]
188 pub fn is_enable_variable_substitution(&self) -> bool {
189 self.enable_variable_substitution
190 }
191
192 /// Sets whether to enable variable substitution
193 ///
194 /// # Parameters
195 ///
196 /// * `enable` - Whether to enable
197 ///
198 /// # Returns
199 ///
200 /// Nothing.
201 #[inline]
202 pub fn set_enable_variable_substitution(&mut self, enable: bool) {
203 self.enable_variable_substitution = enable;
204 }
205
206 /// Gets the maximum depth for variable substitution
207 ///
208 /// # Returns
209 ///
210 /// Returns the maximum depth value
211 #[inline]
212 pub fn max_substitution_depth(&self) -> usize {
213 self.max_substitution_depth
214 }
215
216 /// Creates a read-only prefix view using [`ConfigPrefixView`].
217 ///
218 /// # Parameters
219 ///
220 /// * `prefix` - Prefix
221 ///
222 /// # Returns
223 ///
224 /// Returns a read-only prefix view
225 ///
226 /// # Examples
227 ///
228 /// ```rust
229 /// use qubit_config::{Config, ConfigReader};
230 ///
231 /// let mut config = Config::new();
232 /// config.set("server.port", 8080).unwrap();
233 /// config.set("server.host", "localhost").unwrap();
234 ///
235 /// let server = config.prefix_view("server");
236 /// assert_eq!(server.get::<i32>("port").unwrap(), 8080);
237 /// assert_eq!(server.get::<String>("host").unwrap(), "localhost");
238 /// ```
239 #[inline]
240 pub fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
241 ConfigPrefixView::new(self, prefix)
242 }
243
244 /// Sets the maximum depth for variable substitution
245 ///
246 /// # Parameters
247 ///
248 /// * `depth` - Maximum depth
249 ///
250 /// # Returns
251 ///
252 /// Nothing.
253 #[inline]
254 pub fn set_max_substitution_depth(&mut self, depth: usize) {
255 self.max_substitution_depth = depth;
256 }
257
258 // ========================================================================
259 // Configuration Item Management
260 // ========================================================================
261
262 /// Checks if the configuration contains an item with the specified name
263 ///
264 /// # Parameters
265 ///
266 /// * `name` - Configuration item name
267 ///
268 /// # Returns
269 ///
270 /// Returns `true` if the configuration item exists
271 ///
272 /// # Examples
273 ///
274 /// ```rust
275 /// use qubit_config::Config;
276 ///
277 /// let mut config = Config::new();
278 /// config.set("port", 8080).unwrap();
279 ///
280 /// assert!(config.contains("port"));
281 /// assert!(!config.contains("host"));
282 /// ```
283 #[inline]
284 pub fn contains(&self, name: &str) -> bool {
285 self.properties.contains_key(name)
286 }
287
288 /// Gets a reference to a configuration item
289 ///
290 /// # Parameters
291 ///
292 /// * `name` - Configuration item name
293 ///
294 /// # Returns
295 ///
296 /// Returns Option containing the configuration item
297 #[inline]
298 pub fn get_property(&self, name: &str) -> Option<&Property> {
299 self.properties.get(name)
300 }
301
302 /// Gets a mutable reference to a configuration item
303 ///
304 /// # Parameters
305 ///
306 /// * `name` - Configuration item name
307 ///
308 /// # Returns
309 ///
310 /// Returns mutable Option containing the configuration item
311 #[inline]
312 pub fn get_property_mut(&mut self, name: &str) -> Option<&mut Property> {
313 self.properties.get_mut(name)
314 }
315
316 /// Removes a configuration item
317 ///
318 /// # Parameters
319 ///
320 /// * `name` - Configuration item name
321 ///
322 /// # Returns
323 ///
324 /// Returns the removed configuration item, or None if it doesn't exist
325 ///
326 /// # Examples
327 ///
328 /// ```rust
329 /// use qubit_config::Config;
330 ///
331 /// let mut config = Config::new();
332 /// config.set("port", 8080).unwrap();
333 ///
334 /// let removed = config.remove("port");
335 /// assert!(removed.is_some());
336 /// assert!(!config.contains("port"));
337 /// ```
338 #[inline]
339 pub fn remove(&mut self, name: &str) -> Option<Property> {
340 self.properties.remove(name)
341 }
342
343 /// Clears all configuration items
344 ///
345 /// # Examples
346 ///
347 /// ```rust
348 /// use qubit_config::Config;
349 ///
350 /// let mut config = Config::new();
351 /// config.set("port", 8080).unwrap();
352 /// config.set("host", "localhost").unwrap();
353 ///
354 /// config.clear();
355 /// assert!(config.is_empty());
356 /// ```
357 ///
358 /// # Returns
359 ///
360 /// Nothing.
361 #[inline]
362 pub fn clear(&mut self) {
363 self.properties.clear();
364 }
365
366 /// Gets the number of configuration items
367 ///
368 /// # Returns
369 ///
370 /// Returns the number of configuration items
371 #[inline]
372 pub fn len(&self) -> usize {
373 self.properties.len()
374 }
375
376 /// Checks if the configuration is empty
377 ///
378 /// # Returns
379 ///
380 /// Returns `true` if the configuration contains no items
381 #[inline]
382 pub fn is_empty(&self) -> bool {
383 self.properties.is_empty()
384 }
385
386 /// Gets all configuration item names
387 ///
388 /// # Returns
389 ///
390 /// Returns a Vec of configuration item names
391 ///
392 /// # Examples
393 ///
394 /// ```rust
395 /// use qubit_config::Config;
396 ///
397 /// let mut config = Config::new();
398 /// config.set("port", 8080).unwrap();
399 /// config.set("host", "localhost").unwrap();
400 ///
401 /// let keys = config.keys();
402 /// assert_eq!(keys.len(), 2);
403 /// assert!(keys.contains(&"port".to_string()));
404 /// assert!(keys.contains(&"host".to_string()));
405 /// ```
406 pub fn keys(&self) -> Vec<String> {
407 self.properties.keys().cloned().collect()
408 }
409
410 /// Looks up a property by key for internal read paths.
411 ///
412 /// # Parameters
413 ///
414 /// * `name` - Configuration key
415 ///
416 /// # Returns
417 ///
418 /// `Ok(&Property)` if the key exists, or [`ConfigError::PropertyNotFound`]
419 /// otherwise.
420 #[inline]
421 fn get_property_by_name(&self, name: &str) -> ConfigResult<&Property> {
422 self.properties
423 .get(name)
424 .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))
425 }
426
427 /// Ensures the entry for `name` is not marked final before a write.
428 ///
429 /// Missing keys are allowed (writes may create them).
430 ///
431 /// # Parameters
432 ///
433 /// * `name` - Configuration key
434 ///
435 /// # Returns
436 ///
437 /// `Ok(())` if the key is absent or not final, or
438 /// [`ConfigError::PropertyIsFinal`] if an existing property is final.
439 #[inline]
440 fn ensure_property_not_final(&self, name: &str) -> ConfigResult<()> {
441 if let Some(prop) = self.properties.get(name)
442 && prop.is_final()
443 {
444 return Err(ConfigError::PropertyIsFinal(name.to_string()));
445 }
446 Ok(())
447 }
448
449 /// Shared "optional get" semantics: treat missing or empty properties as
450 /// `None`, otherwise run `read` and wrap the result in `Some`.
451 ///
452 /// Used by [`Self::get_optional`], [`Self::get_optional_list`],
453 /// [`Self::get_optional_string`], and [`Self::get_optional_string_list`].
454 ///
455 /// # Type Parameters
456 ///
457 /// * `T` - Value type produced by `read`
458 ///
459 /// # Parameters
460 ///
461 /// * `name` - Configuration key
462 /// * `read` - Loads `T` when the key exists and is non-empty (typically
463 /// delegates to [`Self::get`], [`Self::get_list`], etc.)
464 ///
465 /// # Returns
466 ///
467 /// `Ok(None)` if the key is missing or the property has no values;
468 /// `Ok(Some(value))` on success; or the error from `read` on failure.
469 fn get_optional_when_present<T>(
470 &self,
471 name: &str,
472 read: impl FnOnce(&Self) -> ConfigResult<T>,
473 ) -> ConfigResult<Option<T>> {
474 match self.properties.get(name) {
475 None => Ok(None),
476 Some(prop) if prop.is_empty() => Ok(None),
477 Some(_) => read(self).map(Some),
478 }
479 }
480
481 // ========================================================================
482 // Core Generic Methods
483 // ========================================================================
484
485 /// Gets a configuration value.
486 ///
487 /// Core read API with type inference.
488 ///
489 /// # Note
490 ///
491 /// This method does not perform variable substitution for string types. If
492 /// you need variable substitution, use [`Self::get_string`].
493 ///
494 /// # Type Parameters
495 ///
496 /// * `T` - Target type, must implement `FromPropertyValue` trait
497 ///
498 /// # Parameters
499 ///
500 /// * `name` - Configuration item name
501 ///
502 /// # Returns
503 ///
504 /// The value of the specified type on success, or a [`ConfigError`] on
505 /// failure.
506 ///
507 /// # Errors
508 ///
509 /// - [`ConfigError::PropertyNotFound`] if the key does not exist
510 /// - [`ConfigError::PropertyHasNoValue`] if the property has no value
511 /// - [`ConfigError::TypeMismatch`] if the type does not match
512 ///
513 /// # Examples
514 ///
515 /// ```rust
516 /// use qubit_config::Config;
517 ///
518 /// let mut config = Config::new();
519 /// config.set("port", 8080).unwrap();
520 /// config.set("host", "localhost").unwrap();
521 ///
522 /// // Method 1: Type inference
523 /// let port: i32 = config.get("port").unwrap();
524 /// let host: String = config.get("host").unwrap();
525 ///
526 /// // Method 2: Turbofish
527 /// let port = config.get::<i32>("port").unwrap();
528 /// let host = config.get::<String>("host").unwrap();
529 ///
530 /// // Method 3: Inference from usage
531 /// fn start_server(port: i32, host: String) { }
532 /// start_server(config.get("port").unwrap(), config.get("host").unwrap());
533 /// ```
534 pub fn get<T>(&self, name: &str) -> ConfigResult<T>
535 where
536 MultiValues: MultiValuesFirstGetter<T>,
537 {
538 let property = self.get_property_by_name(name)?;
539
540 property
541 .get_first::<T>()
542 .map_err(|e| utils::map_value_error(name, e))
543 }
544
545 /// Gets a configuration value or returns a default value
546 ///
547 /// Returns `default` if the key is missing or if reading the value fails.
548 ///
549 /// # Type Parameters
550 ///
551 /// * `T` - Target type, must implement `FromPropertyValue` trait
552 ///
553 /// # Parameters
554 ///
555 /// * `name` - Configuration item name
556 /// * `default` - Default value
557 ///
558 /// # Returns
559 ///
560 /// Returns the configuration value or default value
561 ///
562 /// # Examples
563 ///
564 /// ```rust
565 /// use qubit_config::Config;
566 ///
567 /// let config = Config::new();
568 ///
569 /// let port: i32 = config.get_or("port", 8080);
570 /// let host: String = config.get_or("host", "localhost".to_string());
571 ///
572 /// assert_eq!(port, 8080);
573 /// assert_eq!(host, "localhost");
574 /// ```
575 pub fn get_or<T>(&self, name: &str, default: T) -> T
576 where
577 MultiValues: MultiValuesFirstGetter<T>,
578 {
579 self.get(name).unwrap_or(default)
580 }
581
582 /// Gets a list of configuration values
583 ///
584 /// Gets all values of a configuration item (multi-value configuration).
585 ///
586 /// # Type Parameters
587 ///
588 /// * `T` - Target type, must implement `FromPropertyValue` trait
589 ///
590 /// # Parameters
591 ///
592 /// * `name` - Configuration item name
593 ///
594 /// # Returns
595 ///
596 /// Returns a list of values on success, or an error on failure
597 ///
598 /// # Examples
599 ///
600 /// ```rust
601 /// use qubit_config::Config;
602 ///
603 /// let mut config = Config::new();
604 /// config.set("ports", vec![8080, 8081, 8082]).unwrap();
605 ///
606 /// let ports: Vec<i32> = config.get_list("ports").unwrap();
607 /// assert_eq!(ports, vec![8080, 8081, 8082]);
608 /// ```
609 pub fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
610 where
611 MultiValues: MultiValuesGetter<T>,
612 {
613 let property = self.get_property_by_name(name)?;
614
615 property
616 .get::<T>()
617 .map_err(|e| utils::map_value_error(name, e))
618 }
619
620 /// Sets a configuration value
621 ///
622 /// This is the core method for setting configuration values, supporting
623 /// type inference.
624 ///
625 /// # Type Parameters
626 ///
627 /// * `T` - Element type, automatically inferred from the `values` parameter
628 ///
629 /// # Parameters
630 ///
631 /// * `name` - Configuration item name
632 /// * `values` - Value to store; supports `T`, `Vec<T>`, `&[T]`, and related
633 /// forms accepted by [`MultiValues`] setters
634 ///
635 /// # Returns
636 ///
637 /// Returns Ok(()) on success, or an error on failure
638 ///
639 /// # Errors
640 ///
641 /// - [`ConfigError::PropertyIsFinal`] if the property is marked final
642 ///
643 /// # Examples
644 ///
645 /// ```rust
646 /// use qubit_config::Config;
647 ///
648 /// let mut config = Config::new();
649 ///
650 /// // Set single values (type auto-inference)
651 /// config.set("port", 8080).unwrap(); // T inferred as i32
652 /// config.set("host", "localhost").unwrap();
653 /// // T inferred as String; &str is converted
654 /// config.set("debug", true).unwrap(); // T inferred as bool
655 /// config.set("timeout", 30.5).unwrap(); // T inferred as f64
656 ///
657 /// // Set multiple values (type auto-inference)
658 /// config.set("ports", vec![8080, 8081, 8082]).unwrap(); // T inferred as i32
659 /// config.set("hosts", vec!["host1", "host2"]).unwrap();
660 /// // T inferred as &str (then converted)
661 /// ```
662 pub fn set<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
663 where
664 S: for<'a> MultiValuesSetArg<'a>,
665 <S as MultiValuesSetArg<'static>>::Item: Clone,
666 MultiValues: MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
667 + MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
668 + MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
669 {
670 self.ensure_property_not_final(name)?;
671 let property = self
672 .properties
673 .entry(name.to_string())
674 .or_insert_with(|| Property::new(name));
675
676 property.set(values).map_err(ConfigError::from)
677 }
678
679 /// Adds configuration values
680 ///
681 /// Adds values to an existing configuration item (multi-value properties).
682 ///
683 /// # Type Parameters
684 ///
685 /// * `T` - Element type, automatically inferred from the `values` parameter
686 ///
687 /// # Parameters
688 ///
689 /// * `name` - Configuration item name
690 /// * `values` - Values to append; supports the same forms as [`Self::set`]
691 ///
692 /// # Returns
693 ///
694 /// Returns Ok(()) on success, or an error on failure
695 ///
696 /// # Examples
697 ///
698 /// ```rust
699 /// use qubit_config::Config;
700 ///
701 /// let mut config = Config::new();
702 /// config.set("port", 8080).unwrap(); // Set initial value
703 /// config.add("port", 8081).unwrap(); // Add single value
704 /// config.add("port", vec![8082, 8083]).unwrap(); // Add multiple values
705 /// config.add("port", vec![8084, 8085]).unwrap(); // Add slice
706 ///
707 /// let ports: Vec<i32> = config.get_list("port").unwrap();
708 /// assert_eq!(ports, vec![8080, 8081, 8082, 8083, 8084, 8085]);
709 /// ```
710 pub fn add<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
711 where
712 S: for<'a> MultiValuesAddArg<'a, Item = <S as MultiValuesSetArg<'static>>::Item>
713 + for<'a> MultiValuesSetArg<'a>,
714 <S as MultiValuesSetArg<'static>>::Item: Clone,
715 MultiValues: MultiValuesAdder<<S as MultiValuesSetArg<'static>>::Item>
716 + MultiValuesMultiAdder<<S as MultiValuesSetArg<'static>>::Item>
717 + MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
718 + MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
719 + MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
720 {
721 self.ensure_property_not_final(name)?;
722
723 if let Some(property) = self.properties.get_mut(name) {
724 property.add(values).map_err(ConfigError::from)
725 } else {
726 let mut property = Property::new(name);
727 // Note: property.set() always returns Ok(()) in current MultiValues implementation,
728 // as it unconditionally replaces the entire value without any validation.
729 // We explicitly ignore the result to improve code coverage and avoid unreachable error paths.
730 let _ = property.set(values);
731 self.properties.insert(name.to_string(), property);
732 Ok(())
733 }
734 }
735
736 // ========================================================================
737 // String Special Handling (Variable Substitution)
738 // ========================================================================
739
740 /// Gets a string configuration value (with variable substitution)
741 ///
742 /// If variable substitution is enabled, replaces `${var_name}` placeholders
743 /// in the stored string.
744 ///
745 /// # Parameters
746 ///
747 /// * `name` - Configuration item name
748 ///
749 /// # Returns
750 ///
751 /// Returns the string value on success, or an error on failure
752 ///
753 /// # Examples
754 ///
755 /// ```rust
756 /// use qubit_config::Config;
757 ///
758 /// let mut config = Config::new();
759 /// config.set("base_url", "http://localhost").unwrap();
760 /// config.set("api_url", "${base_url}/api").unwrap();
761 ///
762 /// let api_url = config.get_string("api_url").unwrap();
763 /// assert_eq!(api_url, "http://localhost/api");
764 /// ```
765 pub fn get_string(&self, name: &str) -> ConfigResult<String> {
766 let value: String = self.get(name)?;
767 if self.enable_variable_substitution {
768 utils::substitute_variables(&value, self, self.max_substitution_depth)
769 } else {
770 Ok(value)
771 }
772 }
773
774 /// Gets a string with substitution, or `default` if reading fails.
775 ///
776 /// # Parameters
777 ///
778 /// * `name` - Configuration item name
779 /// * `default` - Default value
780 ///
781 /// # Returns
782 ///
783 /// Returns the string value or default value
784 ///
785 pub fn get_string_or(&self, name: &str, default: &str) -> String {
786 self.get_string(name)
787 .unwrap_or_else(|_| default.to_string())
788 }
789
790 /// Gets a list of string configuration values (with variable substitution)
791 ///
792 /// If variable substitution is enabled, runs it on each list element
793 /// (same `${var_name}` rules as [`Self::get_string`]).
794 ///
795 /// # Parameters
796 ///
797 /// * `name` - Configuration item name
798 ///
799 /// # Returns
800 ///
801 /// Returns a list of strings on success, or an error on failure
802 ///
803 /// # Examples
804 ///
805 /// ```rust
806 /// use qubit_config::Config;
807 ///
808 /// let mut config = Config::new();
809 /// config.set("base_path", "/opt/app").unwrap();
810 /// config.set("paths", vec!["${base_path}/bin", "${base_path}/lib"]).unwrap();
811 ///
812 /// let paths = config.get_string_list("paths").unwrap();
813 /// assert_eq!(paths, vec!["/opt/app/bin", "/opt/app/lib"]);
814 /// ```
815 pub fn get_string_list(&self, name: &str) -> ConfigResult<Vec<String>> {
816 let values: Vec<String> = self.get_list(name)?;
817 if self.enable_variable_substitution {
818 values
819 .into_iter()
820 .map(|v| utils::substitute_variables(&v, self, self.max_substitution_depth))
821 .collect()
822 } else {
823 Ok(values)
824 }
825 }
826
827 /// Gets a list of string configuration values or returns a default value
828 /// (with variable substitution)
829 ///
830 /// # Parameters
831 ///
832 /// * `name` - Configuration item name
833 /// * `default` - Default value (can be array slice or vec)
834 ///
835 /// # Returns
836 ///
837 /// Returns the list of strings or default value
838 ///
839 /// # Examples
840 ///
841 /// ```rust
842 /// use qubit_config::Config;
843 ///
844 /// let config = Config::new();
845 ///
846 /// // Using array slice
847 /// let paths = config.get_string_list_or("paths", &["/default/path"]);
848 /// assert_eq!(paths, vec!["/default/path"]);
849 ///
850 /// // Using vec
851 /// let paths = config.get_string_list_or("paths", &vec!["path1", "path2"]);
852 /// assert_eq!(paths, vec!["path1", "path2"]);
853 /// ```
854 pub fn get_string_list_or(&self, name: &str, default: &[&str]) -> Vec<String> {
855 self.get_string_list(name)
856 .unwrap_or_else(|_| default.iter().map(|s| s.to_string()).collect())
857 }
858
859 // ========================================================================
860 // Configuration Source Integration
861 // ========================================================================
862
863 /// Merges configuration from a `ConfigSource`
864 ///
865 /// Loads all key-value pairs from the given source and merges them into
866 /// this configuration. Existing non-final properties are overwritten;
867 /// final properties are preserved and cause an error if the source tries
868 /// to overwrite them.
869 ///
870 /// # Parameters
871 ///
872 /// * `source` - The configuration source to load from
873 ///
874 /// # Returns
875 ///
876 /// Returns `Ok(())` on success, or a `ConfigError` on failure
877 ///
878 /// # Examples
879 ///
880 /// ```rust
881 /// use qubit_config::Config;
882 /// use qubit_config::source::{
883 /// CompositeConfigSource, ConfigSource,
884 /// EnvConfigSource, TomlConfigSource,
885 /// };
886 ///
887 /// let mut composite = CompositeConfigSource::new();
888 /// let path = std::env::temp_dir().join(format!(
889 /// "qubit-config-doc-{}.toml",
890 /// std::process::id()
891 /// ));
892 /// std::fs::write(&path, "app.name = \"demo\"").unwrap();
893 /// composite.add(TomlConfigSource::from_file(&path));
894 /// composite.add(EnvConfigSource::with_prefix("APP_"));
895 ///
896 /// let mut config = Config::new();
897 /// config.merge_from_source(&composite).unwrap();
898 /// std::fs::remove_file(&path).unwrap();
899 /// ```
900 #[inline]
901 pub fn merge_from_source(&mut self, source: &dyn ConfigSource) -> ConfigResult<()> {
902 source.load(self)
903 }
904
905 // ========================================================================
906 // Prefix Traversal and Sub-tree Extraction (v0.4.0)
907 // ========================================================================
908
909 /// Iterates over all configuration entries as `(key, &Property)` pairs.
910 ///
911 /// # Returns
912 ///
913 /// An iterator yielding `(&str, &Property)` tuples.
914 ///
915 /// # Examples
916 ///
917 /// ```rust
918 /// use qubit_config::Config;
919 ///
920 /// let mut config = Config::new();
921 /// config.set("host", "localhost").unwrap();
922 /// config.set("port", 8080).unwrap();
923 ///
924 /// for (key, prop) in config.iter() {
925 /// println!("{} = {:?}", key, prop);
926 /// }
927 /// ```
928 #[inline]
929 pub fn iter(&self) -> impl Iterator<Item = (&str, &Property)> {
930 self.properties.iter().map(|(k, v)| (k.as_str(), v))
931 }
932
933 /// Iterates over all configuration entries whose key starts with `prefix`.
934 ///
935 /// # Parameters
936 ///
937 /// * `prefix` - The key prefix to filter by (e.g., `"http."`)
938 ///
939 /// # Returns
940 ///
941 /// An iterator of `(&str, &Property)` whose keys start with `prefix`.
942 ///
943 /// # Examples
944 ///
945 /// ```rust
946 /// use qubit_config::Config;
947 ///
948 /// let mut config = Config::new();
949 /// config.set("http.host", "localhost").unwrap();
950 /// config.set("http.port", 8080).unwrap();
951 /// config.set("db.host", "dbhost").unwrap();
952 ///
953 /// let http_entries: Vec<_> = config.iter_prefix("http.").collect();
954 /// assert_eq!(http_entries.len(), 2);
955 /// ```
956 #[inline]
957 pub fn iter_prefix<'a>(
958 &'a self,
959 prefix: &'a str,
960 ) -> impl Iterator<Item = (&'a str, &'a Property)> {
961 self.properties
962 .iter()
963 .filter(move |(k, _)| k.starts_with(prefix))
964 .map(|(k, v)| (k.as_str(), v))
965 }
966
967 /// Returns `true` if any configuration key starts with `prefix`.
968 ///
969 /// # Parameters
970 ///
971 /// * `prefix` - The key prefix to check
972 ///
973 /// # Returns
974 ///
975 /// `true` if at least one key starts with `prefix`, `false` otherwise.
976 ///
977 /// # Examples
978 ///
979 /// ```rust
980 /// use qubit_config::Config;
981 ///
982 /// let mut config = Config::new();
983 /// config.set("http.host", "localhost").unwrap();
984 ///
985 /// assert!(config.contains_prefix("http."));
986 /// assert!(!config.contains_prefix("db."));
987 /// ```
988 #[inline]
989 pub fn contains_prefix(&self, prefix: &str) -> bool {
990 self.properties.keys().any(|k| k.starts_with(prefix))
991 }
992
993 /// Extracts a sub-configuration for keys matching `prefix`.
994 ///
995 /// # Parameters
996 ///
997 /// * `prefix` - The key prefix to extract (e.g., `"http"`)
998 /// * `strip_prefix` - When `true`, removes `prefix` and the following dot
999 /// from keys in the result; when `false`, keys are copied unchanged.
1000 ///
1001 /// # Returns
1002 ///
1003 /// A new `Config` containing only the matching entries.
1004 ///
1005 /// # Examples
1006 ///
1007 /// ```rust
1008 /// use qubit_config::Config;
1009 ///
1010 /// let mut config = Config::new();
1011 /// config.set("http.host", "localhost").unwrap();
1012 /// config.set("http.port", 8080).unwrap();
1013 /// config.set("db.host", "dbhost").unwrap();
1014 ///
1015 /// let http_config = config.subconfig("http", true).unwrap();
1016 /// assert!(http_config.contains("host"));
1017 /// assert!(http_config.contains("port"));
1018 /// assert!(!http_config.contains("db.host"));
1019 /// ```
1020 pub fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
1021 let mut sub = Config::new();
1022 sub.enable_variable_substitution = self.enable_variable_substitution;
1023 sub.max_substitution_depth = self.max_substitution_depth;
1024
1025 // Empty prefix means "all keys"
1026 if prefix.is_empty() {
1027 for (k, v) in &self.properties {
1028 sub.properties.insert(k.clone(), v.clone());
1029 }
1030 return Ok(sub);
1031 }
1032
1033 let full_prefix = format!("{prefix}.");
1034
1035 for (k, v) in &self.properties {
1036 if k == prefix || k.starts_with(&full_prefix) {
1037 let new_key = if strip_prefix {
1038 if k == prefix {
1039 prefix.to_string()
1040 } else {
1041 k[full_prefix.len()..].to_string()
1042 }
1043 } else {
1044 k.clone()
1045 };
1046 sub.properties.insert(new_key, v.clone());
1047 }
1048 }
1049
1050 Ok(sub)
1051 }
1052
1053 // ========================================================================
1054 // Optional and Null Semantics (v0.4.0)
1055 // ========================================================================
1056
1057 /// Returns `true` if the property exists but has no value (empty / null).
1058 ///
1059 /// This distinguishes between:
1060 /// - Key does not exist → `contains()` returns `false`
1061 /// - Key exists but is empty/null → `is_null()` returns `true`
1062 ///
1063 /// # Parameters
1064 ///
1065 /// * `name` - Configuration item name
1066 ///
1067 /// # Returns
1068 ///
1069 /// `true` if the property exists and has no values (is empty).
1070 ///
1071 /// # Examples
1072 ///
1073 /// ```rust
1074 /// use qubit_config::Config;
1075 /// use qubit_common::DataType;
1076 ///
1077 /// let mut config = Config::new();
1078 /// config.set_null("nullable", DataType::String).unwrap();
1079 ///
1080 /// assert!(config.is_null("nullable"));
1081 /// assert!(!config.is_null("missing"));
1082 /// ```
1083 pub fn is_null(&self, name: &str) -> bool {
1084 self.properties
1085 .get(name)
1086 .map(|p| p.is_empty())
1087 .unwrap_or(false)
1088 }
1089
1090 /// Gets an optional configuration value.
1091 ///
1092 /// Distinguishes between three states:
1093 /// - `Ok(Some(value))` – key exists and has a value
1094 /// - `Ok(None)` – key does not exist, **or** exists but is null/empty
1095 /// - `Err(e)` – key exists and has a value, but conversion failed
1096 ///
1097 /// # Type Parameters
1098 ///
1099 /// * `T` - Target type
1100 ///
1101 /// # Parameters
1102 ///
1103 /// * `name` - Configuration item name
1104 ///
1105 /// # Returns
1106 ///
1107 /// `Ok(Some(value))`, `Ok(None)`, or `Err` as described above.
1108 ///
1109 /// # Examples
1110 ///
1111 /// ```rust
1112 /// use qubit_config::Config;
1113 ///
1114 /// let mut config = Config::new();
1115 /// config.set("port", 8080).unwrap();
1116 ///
1117 /// let port: Option<i32> = config.get_optional("port").unwrap();
1118 /// assert_eq!(port, Some(8080));
1119 ///
1120 /// let missing: Option<i32> = config.get_optional("missing").unwrap();
1121 /// assert_eq!(missing, None);
1122 /// ```
1123 pub fn get_optional<T>(&self, name: &str) -> ConfigResult<Option<T>>
1124 where
1125 MultiValues: MultiValuesFirstGetter<T>,
1126 {
1127 self.get_optional_when_present(name, |c| c.get(name))
1128 }
1129
1130 /// Gets an optional list of configuration values.
1131 ///
1132 /// See also [`Self::get_optional_string_list`] for optional string lists
1133 /// with variable substitution.
1134 ///
1135 /// Distinguishes between three states:
1136 /// - `Ok(Some(vec))` – key exists and has values
1137 /// - `Ok(None)` – key does not exist, **or** exists but is null/empty
1138 /// - `Err(e)` – key exists and has values, but conversion failed
1139 ///
1140 /// # Type Parameters
1141 ///
1142 /// * `T` - Target element type
1143 ///
1144 /// # Parameters
1145 ///
1146 /// * `name` - Configuration item name
1147 ///
1148 /// # Returns
1149 ///
1150 /// `Ok(Some(vec))`, `Ok(None)`, or `Err` as described above.
1151 ///
1152 /// # Examples
1153 ///
1154 /// ```rust
1155 /// use qubit_config::Config;
1156 ///
1157 /// let mut config = Config::new();
1158 /// config.set("ports", vec![8080, 8081]).unwrap();
1159 ///
1160 /// let ports: Option<Vec<i32>> = config.get_optional_list("ports").unwrap();
1161 /// assert_eq!(ports, Some(vec![8080, 8081]));
1162 ///
1163 /// let missing: Option<Vec<i32>> = config.get_optional_list("missing").unwrap();
1164 /// assert_eq!(missing, None);
1165 /// ```
1166 pub fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
1167 where
1168 MultiValues: MultiValuesGetter<T>,
1169 {
1170 self.get_optional_when_present(name, |c| c.get_list(name))
1171 }
1172
1173 /// Gets an optional string (with variable substitution when enabled).
1174 ///
1175 /// Same semantics as [`Self::get_optional`], but values are read via
1176 /// [`Self::get_string`], so `${...}` substitution applies when enabled.
1177 ///
1178 /// # Parameters
1179 ///
1180 /// * `name` - Configuration item name
1181 ///
1182 /// # Returns
1183 ///
1184 /// `Ok(Some(s))`, `Ok(None)`, or `Err` as for [`Self::get_optional`].
1185 ///
1186 /// # Examples
1187 ///
1188 /// ```rust
1189 /// use qubit_config::Config;
1190 ///
1191 /// let mut config = Config::new();
1192 /// config.set("base", "http://localhost").unwrap();
1193 /// config.set("api", "${base}/api").unwrap();
1194 ///
1195 /// let api = config.get_optional_string("api").unwrap();
1196 /// assert_eq!(api.as_deref(), Some("http://localhost/api"));
1197 ///
1198 /// let missing = config.get_optional_string("missing").unwrap();
1199 /// assert_eq!(missing, None);
1200 /// ```
1201 pub fn get_optional_string(&self, name: &str) -> ConfigResult<Option<String>> {
1202 self.get_optional_when_present(name, |c| c.get_string(name))
1203 }
1204
1205 /// Gets an optional string list (substitution per element when enabled).
1206 ///
1207 /// Same semantics as [`Self::get_optional_list`], but elements use
1208 /// [`Self::get_string_list`] (same `${...}` rules as [`Self::get_string`]).
1209 ///
1210 /// # Parameters
1211 ///
1212 /// * `name` - Configuration item name
1213 ///
1214 /// # Returns
1215 ///
1216 /// `Ok(Some(vec))`, `Ok(None)`, or `Err` like [`Self::get_optional_list`].
1217 ///
1218 /// # Examples
1219 ///
1220 /// ```rust
1221 /// use qubit_config::Config;
1222 ///
1223 /// let mut config = Config::new();
1224 /// config.set("root", "/opt/app").unwrap();
1225 /// config.set("paths", vec!["${root}/bin", "${root}/lib"]).unwrap();
1226 ///
1227 /// let paths = config.get_optional_string_list("paths").unwrap();
1228 /// assert_eq!(
1229 /// paths,
1230 /// Some(vec![
1231 /// "/opt/app/bin".to_string(),
1232 /// "/opt/app/lib".to_string(),
1233 /// ]),
1234 /// );
1235 /// ```
1236 pub fn get_optional_string_list(&self, name: &str) -> ConfigResult<Option<Vec<String>>> {
1237 self.get_optional_when_present(name, |c| c.get_string_list(name))
1238 }
1239
1240 // ========================================================================
1241 // Structured Config Deserialization (v0.4.0)
1242 // ========================================================================
1243
1244 /// Deserializes the subtree at `prefix` into `T` using `serde`.
1245 ///
1246 /// Keys under `prefix` (prefix and trailing dot removed) form a flat map
1247 /// for `serde`, for example:
1248 ///
1249 /// ```rust
1250 /// #[derive(serde::Deserialize)]
1251 /// struct HttpOptions {
1252 /// host: String,
1253 /// port: u16,
1254 /// }
1255 /// ```
1256 ///
1257 /// can be populated from config keys `http.host` and `http.port` by calling
1258 /// `config.deserialize::<HttpOptions>("http")`.
1259 ///
1260 /// # Type Parameters
1261 ///
1262 /// * `T` - Target type, must implement `serde::de::DeserializeOwned`
1263 ///
1264 /// # Parameters
1265 ///
1266 /// * `prefix` - Key prefix for the struct fields (`""` means the root map)
1267 ///
1268 /// # Returns
1269 ///
1270 /// The deserialized `T`, or a [`ConfigError::DeserializeError`] on failure.
1271 ///
1272 /// # Examples
1273 ///
1274 /// ```rust
1275 /// use qubit_config::Config;
1276 /// use serde::Deserialize;
1277 ///
1278 /// #[derive(Deserialize, Debug, PartialEq)]
1279 /// struct Server {
1280 /// host: String,
1281 /// port: i32,
1282 /// }
1283 ///
1284 /// let mut config = Config::new();
1285 /// config.set("server.host", "localhost").unwrap();
1286 /// config.set("server.port", 8080).unwrap();
1287 ///
1288 /// let server: Server = config.deserialize("server").unwrap();
1289 /// assert_eq!(server.host, "localhost");
1290 /// assert_eq!(server.port, 8080);
1291 /// ```
1292 pub fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
1293 where
1294 T: DeserializeOwned,
1295 {
1296 let sub = self.subconfig(prefix, true)?;
1297
1298 let mut map = Map::new();
1299 for (key, prop) in &sub.properties {
1300 let json_val = utils::property_to_json_value(prop);
1301 utils::insert_deserialize_value(&mut map, key, json_val);
1302 }
1303
1304 let json_obj = Value::Object(map);
1305
1306 from_value(json_obj).map_err(|e| ConfigError::DeserializeError {
1307 path: prefix.to_string(),
1308 message: e.to_string(),
1309 })
1310 }
1311
1312 /// Inserts or replaces a property using an explicit [`Property`] object.
1313 ///
1314 /// This method enforces two invariants:
1315 ///
1316 /// - `name` must exactly match `property.name()`
1317 /// - existing final properties cannot be overridden
1318 ///
1319 /// # Parameters
1320 ///
1321 /// * `name` - Target key in this config.
1322 /// * `property` - Property to store under `name`.
1323 ///
1324 /// # Returns
1325 ///
1326 /// `Ok(())` on success.
1327 ///
1328 /// # Errors
1329 ///
1330 /// - [`ConfigError::MergeError`] when `name` and `property.name()` differ.
1331 /// - [`ConfigError::PropertyIsFinal`] when trying to override a final
1332 /// property.
1333 pub fn insert_property(&mut self, name: &str, property: Property) -> ConfigResult<()> {
1334 if property.name() != name {
1335 return Err(ConfigError::MergeError(format!(
1336 "Property name mismatch: key '{name}' != property '{}'",
1337 property.name()
1338 )));
1339 }
1340 self.ensure_property_not_final(name)?;
1341 self.properties.insert(name.to_string(), property);
1342 Ok(())
1343 }
1344
1345 /// Sets a key to a typed null/empty value.
1346 ///
1347 /// This is the preferred public API for representing null/empty values
1348 /// without exposing raw mutable access to the internal map.
1349 ///
1350 /// # Parameters
1351 ///
1352 /// * `name` - Configuration item name.
1353 /// * `data_type` - Data type metadata for the empty value.
1354 ///
1355 /// # Returns
1356 ///
1357 /// `Ok(())` on success.
1358 ///
1359 /// # Errors
1360 ///
1361 /// - [`ConfigError::PropertyIsFinal`] when trying to override a final
1362 /// property.
1363 #[inline]
1364 pub fn set_null(&mut self, name: &str, data_type: DataType) -> ConfigResult<()> {
1365 self.insert_property(
1366 name,
1367 Property::with_value(name, MultiValues::Empty(data_type)),
1368 )
1369 }
1370}
1371
1372impl ConfigReader for Config {
1373 #[inline]
1374 fn is_enable_variable_substitution(&self) -> bool {
1375 Config::is_enable_variable_substitution(self)
1376 }
1377
1378 #[inline]
1379 fn max_substitution_depth(&self) -> usize {
1380 Config::max_substitution_depth(self)
1381 }
1382
1383 #[inline]
1384 fn description(&self) -> Option<&str> {
1385 Config::description(self)
1386 }
1387
1388 #[inline]
1389 fn get_property(&self, name: &str) -> Option<&Property> {
1390 Config::get_property(self, name)
1391 }
1392
1393 #[inline]
1394 fn len(&self) -> usize {
1395 Config::len(self)
1396 }
1397
1398 #[inline]
1399 fn is_empty(&self) -> bool {
1400 Config::is_empty(self)
1401 }
1402
1403 #[inline]
1404 fn keys(&self) -> Vec<String> {
1405 Config::keys(self)
1406 }
1407
1408 #[inline]
1409 fn contains(&self, name: &str) -> bool {
1410 Config::contains(self, name)
1411 }
1412
1413 #[inline]
1414 fn get<T>(&self, name: &str) -> ConfigResult<T>
1415 where
1416 MultiValues: MultiValuesFirstGetter<T>,
1417 {
1418 Config::get(self, name)
1419 }
1420
1421 #[inline]
1422 fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
1423 where
1424 MultiValues: MultiValuesGetter<T>,
1425 {
1426 Config::get_list(self, name)
1427 }
1428
1429 #[inline]
1430 fn get_optional<T>(&self, name: &str) -> ConfigResult<Option<T>>
1431 where
1432 MultiValues: MultiValuesFirstGetter<T>,
1433 {
1434 Config::get_optional(self, name)
1435 }
1436
1437 #[inline]
1438 fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
1439 where
1440 MultiValues: MultiValuesGetter<T>,
1441 {
1442 Config::get_optional_list(self, name)
1443 }
1444
1445 #[inline]
1446 fn contains_prefix(&self, prefix: &str) -> bool {
1447 Config::contains_prefix(self, prefix)
1448 }
1449
1450 #[inline]
1451 fn iter_prefix<'a>(
1452 &'a self,
1453 prefix: &'a str,
1454 ) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
1455 Box::new(Config::iter_prefix(self, prefix))
1456 }
1457
1458 #[inline]
1459 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
1460 Box::new(Config::iter(self))
1461 }
1462
1463 #[inline]
1464 fn is_null(&self, name: &str) -> bool {
1465 Config::is_null(self, name)
1466 }
1467
1468 #[inline]
1469 fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
1470 Config::subconfig(self, prefix, strip_prefix)
1471 }
1472
1473 #[inline]
1474 fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
1475 where
1476 T: DeserializeOwned,
1477 {
1478 Config::deserialize(self, prefix)
1479 }
1480
1481 #[inline]
1482 fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
1483 Config::prefix_view(self, prefix)
1484 }
1485}
1486
1487impl Default for Config {
1488 /// Creates a new default configuration
1489 ///
1490 /// # Returns
1491 ///
1492 /// Returns a new configuration instance
1493 ///
1494 /// # Examples
1495 ///
1496 /// ```rust
1497 /// use qubit_config::Config;
1498 ///
1499 /// let config = Config::default();
1500 /// assert!(config.is_empty());
1501 /// ```
1502 #[inline]
1503 fn default() -> Self {
1504 Self::new()
1505 }
1506}