qubit_config/config.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # Configuration Manager
11//!
12//! Provides storage, retrieval, and management of configurations.
13//!
14
15#![allow(private_bounds)]
16
17use serde::de::DeserializeOwned;
18use serde::{Deserialize, Serialize};
19use serde_json::{Map, Value as JsonValue, from_value};
20use std::collections::HashMap;
21use std::path::Path;
22
23use crate::ConfigPropertyMut;
24use crate::config_prefix_view::ConfigPrefixView;
25use crate::config_reader::ConfigReader;
26use crate::constants::DEFAULT_MAX_SUBSTITUTION_DEPTH;
27use crate::field::ConfigField;
28use crate::from::FromConfig;
29use crate::options::ConfigReadOptions;
30use crate::source::{
31 ConfigSource, EnvConfigSource, EnvFileConfigSource, PropertiesConfigSource, TomlConfigSource,
32 YamlConfigSource,
33};
34use crate::utils;
35use crate::{ConfigError, ConfigResult, Property};
36use qubit_datatype::DataType;
37use qubit_value::MultiValues;
38use qubit_value::multi_values::{
39 MultiValuesAddArg, MultiValuesAdder, MultiValuesFirstGetter, MultiValuesGetter,
40 MultiValuesMultiAdder, MultiValuesSetArg, MultiValuesSetter, MultiValuesSetterSlice,
41 MultiValuesSingleSetter,
42};
43
44/// Configuration Manager
45///
46/// Manages a set of configuration properties with type-safe read/write
47/// interfaces.
48///
49/// # Features
50///
51/// - Supports multiple data types
52/// - Supports variable substitution (`${var_name}` format)
53/// - Supports configuration merging
54/// - Supports final value protection
55/// - Thread-safe (when wrapped in `Arc<RwLock<Config>>`)
56///
57/// # Examples
58///
59/// ```rust
60/// use qubit_config::Config;
61///
62/// let mut config = Config::new();
63///
64/// // Set configuration values (type inference)
65/// config.set("port", 8080).unwrap(); // inferred as i32
66/// config.set("host", "localhost").unwrap();
67/// // &str is converted to String
68/// config.set("debug", true).unwrap(); // inferred as bool
69/// config.set("timeout", 30.5).unwrap(); // inferred as f64
70/// config.set("code", 42u8).unwrap(); // inferred as u8
71///
72/// // Set multiple values (type inference)
73/// config.set("ports", vec![8080, 8081, 8082]).unwrap(); // inferred as i32
74/// config.set("hosts", vec!["host1", "host2"]).unwrap();
75/// // &str elements are converted
76///
77/// // Read configuration values (type inference)
78/// let port: i32 = config.get("port").unwrap();
79/// let host: String = config.get("host").unwrap();
80/// let debug: bool = config.get("debug").unwrap();
81/// let code: u8 = config.get("code").unwrap();
82///
83/// // Read configuration values (turbofish)
84/// let port = config.get::<i32>("port").unwrap();
85///
86/// // Read configuration value or use default
87/// let timeout: u64 = config.get_or("timeout", 30).unwrap();
88/// ```
89///
90///
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct Config {
93 /// Configuration description
94 description: Option<String>,
95 /// Configuration property mapping
96 pub(crate) properties: HashMap<String, Property>,
97 /// Whether variable substitution is enabled
98 enable_variable_substitution: bool,
99 /// Maximum depth for variable substitution
100 max_substitution_depth: usize,
101 /// Runtime read parsing options
102 #[serde(skip, default)]
103 read_options: ConfigReadOptions,
104}
105
106impl Config {
107 /// Creates a new empty configuration
108 ///
109 /// # Returns
110 ///
111 /// Returns a new configuration instance
112 ///
113 /// # Examples
114 ///
115 /// ```rust
116 /// use qubit_config::Config;
117 ///
118 /// let mut config = Config::new();
119 /// assert!(config.is_empty());
120 /// ```
121 #[inline]
122 pub fn new() -> Self {
123 Self {
124 description: None,
125 properties: HashMap::new(),
126 enable_variable_substitution: true,
127 max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
128 read_options: ConfigReadOptions::default(),
129 }
130 }
131
132 /// Creates a configuration with description
133 ///
134 /// # Parameters
135 ///
136 /// * `description` - Configuration description
137 ///
138 /// # Returns
139 ///
140 /// Returns a new configuration instance
141 ///
142 /// # Examples
143 ///
144 /// ```rust
145 /// use qubit_config::Config;
146 ///
147 /// let config = Config::with_description("Server Configuration");
148 /// assert_eq!(config.description(), Some("Server Configuration"));
149 /// ```
150 #[inline]
151 pub fn with_description(description: &str) -> Self {
152 Self {
153 description: Some(description.to_string()),
154 properties: HashMap::new(),
155 enable_variable_substitution: true,
156 max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
157 read_options: ConfigReadOptions::default(),
158 }
159 }
160
161 // ========================================================================
162 // Basic Property Access
163 // ========================================================================
164
165 /// Gets the configuration description
166 ///
167 /// # Returns
168 ///
169 /// Returns the configuration description as Option
170 #[inline]
171 pub fn description(&self) -> Option<&str> {
172 self.description.as_deref()
173 }
174
175 /// Sets the configuration description
176 ///
177 /// # Parameters
178 ///
179 /// * `description` - Configuration description
180 ///
181 /// # Returns
182 ///
183 /// Nothing.
184 #[inline]
185 pub fn set_description(&mut self, description: Option<String>) {
186 self.description = description;
187 }
188
189 /// Checks if variable substitution is enabled
190 ///
191 /// # Returns
192 ///
193 /// Returns `true` if variable substitution is enabled
194 #[inline]
195 pub fn is_enable_variable_substitution(&self) -> bool {
196 self.enable_variable_substitution
197 }
198
199 /// Sets whether to enable variable substitution
200 ///
201 /// # Parameters
202 ///
203 /// * `enable` - Whether to enable
204 ///
205 /// # Returns
206 ///
207 /// Nothing.
208 #[inline]
209 pub fn set_enable_variable_substitution(&mut self, enable: bool) {
210 self.enable_variable_substitution = enable;
211 }
212
213 /// Gets the maximum depth for variable substitution
214 ///
215 /// # Returns
216 ///
217 /// Returns the maximum depth value
218 #[inline]
219 pub fn max_substitution_depth(&self) -> usize {
220 self.max_substitution_depth
221 }
222
223 /// Gets the global read parsing options.
224 ///
225 /// # Returns
226 ///
227 /// The options used by `get`, `get_any`, and field reads when no
228 /// field-level override is provided.
229 #[inline]
230 pub fn read_options(&self) -> &ConfigReadOptions {
231 &self.read_options
232 }
233
234 /// Sets the global read parsing options.
235 ///
236 /// # Parameters
237 ///
238 /// * `read_options` - New read parsing options.
239 ///
240 /// # Returns
241 ///
242 /// Mutable reference to this configuration for chaining.
243 #[inline]
244 pub fn set_read_options(&mut self, read_options: ConfigReadOptions) -> &mut Self {
245 self.read_options = read_options;
246 self
247 }
248
249 /// Returns a cloned configuration with different read parsing options.
250 ///
251 /// # Parameters
252 ///
253 /// * `read_options` - Read options for the returned configuration.
254 ///
255 /// # Returns
256 ///
257 /// A cloned [`Config`] using `read_options`.
258 #[must_use]
259 pub fn with_read_options(&self, read_options: ConfigReadOptions) -> Self {
260 let mut config = self.clone();
261 config.read_options = read_options;
262 config
263 }
264
265 /// Creates a read-only prefix view using [`ConfigPrefixView`].
266 ///
267 /// # Parameters
268 ///
269 /// * `prefix` - Prefix
270 ///
271 /// # Returns
272 ///
273 /// Returns a read-only prefix view
274 ///
275 /// # Examples
276 ///
277 /// ```rust
278 /// use qubit_config::{Config, ConfigReader};
279 ///
280 /// let mut config = Config::new();
281 /// config.set("server.port", 8080).unwrap();
282 /// config.set("server.host", "localhost").unwrap();
283 ///
284 /// let server = config.prefix_view("server");
285 /// assert_eq!(server.get::<i32>("port").unwrap(), 8080);
286 /// assert_eq!(server.get::<String>("host").unwrap(), "localhost");
287 /// ```
288 #[inline]
289 pub fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
290 ConfigPrefixView::new(self, prefix)
291 }
292
293 /// Sets the maximum depth for variable substitution
294 ///
295 /// # Parameters
296 ///
297 /// * `depth` - Maximum depth
298 ///
299 /// # Returns
300 ///
301 /// Nothing.
302 #[inline]
303 pub fn set_max_substitution_depth(&mut self, depth: usize) {
304 self.max_substitution_depth = depth;
305 }
306
307 // ========================================================================
308 // Configuration Item Management
309 // ========================================================================
310
311 /// Checks if the configuration contains an item with the specified name
312 ///
313 /// # Parameters
314 ///
315 /// * `name` - Configuration item name
316 ///
317 /// # Returns
318 ///
319 /// Returns `true` if the configuration item exists
320 ///
321 /// # Examples
322 ///
323 /// ```rust
324 /// use qubit_config::Config;
325 ///
326 /// let mut config = Config::new();
327 /// config.set("port", 8080).unwrap();
328 ///
329 /// assert!(config.contains("port"));
330 /// assert!(!config.contains("host"));
331 /// ```
332 #[inline]
333 pub fn contains(&self, name: &str) -> bool {
334 self.properties.contains_key(name)
335 }
336
337 /// Gets a reference to a configuration item
338 ///
339 /// # Parameters
340 ///
341 /// * `name` - Configuration item name
342 ///
343 /// # Returns
344 ///
345 /// Returns Option containing the configuration item
346 #[inline]
347 pub fn get_property(&self, name: &str) -> Option<&Property> {
348 self.properties.get(name)
349 }
350
351 /// Gets guarded mutable access to a non-final configuration item.
352 ///
353 /// # Parameters
354 ///
355 /// * `name` - Configuration item name
356 ///
357 /// # Returns
358 ///
359 /// Returns `Ok(Some(_))` for an existing non-final property, `Ok(None)`
360 /// for a missing property, or [`ConfigError::PropertyIsFinal`] for an
361 /// existing final property. The returned guard re-checks final state before
362 /// each value-changing operation.
363 #[inline]
364 pub fn get_property_mut(&mut self, name: &str) -> ConfigResult<Option<ConfigPropertyMut<'_>>> {
365 self.ensure_property_not_final(name)?;
366 Ok(self.properties.get_mut(name).map(ConfigPropertyMut::new))
367 }
368
369 /// Sets the final flag of an existing configuration item.
370 ///
371 /// A non-final property can be marked final. A property that is already
372 /// final may be marked final again, but cannot be unset through this API.
373 ///
374 /// # Parameters
375 ///
376 /// * `name` - Configuration item name.
377 /// * `is_final` - Whether the property should be final.
378 ///
379 /// # Returns
380 ///
381 /// `Ok(())` on success.
382 ///
383 /// # Errors
384 ///
385 /// - [`ConfigError::PropertyNotFound`] if the key does not exist.
386 /// - [`ConfigError::PropertyIsFinal`] when trying to unset a final
387 /// property.
388 pub fn set_final(&mut self, name: &str, is_final: bool) -> ConfigResult<()> {
389 let property = self
390 .properties
391 .get_mut(name)
392 .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
393 if property.is_final() && !is_final {
394 return Err(ConfigError::PropertyIsFinal(name.to_string()));
395 }
396 property.set_final(is_final);
397 Ok(())
398 }
399
400 /// Removes a non-final configuration item.
401 ///
402 /// # Parameters
403 ///
404 /// * `name` - Configuration item name
405 ///
406 /// # Returns
407 ///
408 /// Returns the removed configuration item, or None if it doesn't exist
409 ///
410 /// # Examples
411 ///
412 /// ```rust
413 /// use qubit_config::Config;
414 ///
415 /// let mut config = Config::new();
416 /// config.set("port", 8080).unwrap();
417 ///
418 /// let removed = config.remove("port").unwrap();
419 /// assert!(removed.is_some());
420 /// assert!(!config.contains("port"));
421 /// ```
422 #[inline]
423 pub fn remove(&mut self, name: &str) -> ConfigResult<Option<Property>> {
424 self.ensure_property_not_final(name)?;
425 Ok(self.properties.remove(name))
426 }
427
428 /// Clears all configuration items if none of them are final.
429 ///
430 /// # Examples
431 ///
432 /// ```rust
433 /// use qubit_config::Config;
434 ///
435 /// let mut config = Config::new();
436 /// config.set("port", 8080).unwrap();
437 /// config.set("host", "localhost").unwrap();
438 ///
439 /// config.clear().unwrap();
440 /// assert!(config.is_empty());
441 /// ```
442 ///
443 /// # Returns
444 ///
445 /// `Ok(())` when all properties were removed.
446 #[inline]
447 pub fn clear(&mut self) -> ConfigResult<()> {
448 self.ensure_no_final_properties()?;
449 self.properties.clear();
450 Ok(())
451 }
452
453 /// Gets the number of configuration items
454 ///
455 /// # Returns
456 ///
457 /// Returns the number of configuration items
458 #[inline]
459 pub fn len(&self) -> usize {
460 self.properties.len()
461 }
462
463 /// Checks if the configuration is empty
464 ///
465 /// # Returns
466 ///
467 /// Returns `true` if the configuration contains no items
468 #[inline]
469 pub fn is_empty(&self) -> bool {
470 self.properties.is_empty()
471 }
472
473 /// Gets all configuration item names
474 ///
475 /// # Returns
476 ///
477 /// Returns a Vec of configuration item names
478 ///
479 /// # Examples
480 ///
481 /// ```rust
482 /// use qubit_config::Config;
483 ///
484 /// let mut config = Config::new();
485 /// config.set("port", 8080).unwrap();
486 /// config.set("host", "localhost").unwrap();
487 ///
488 /// let keys = config.keys();
489 /// assert_eq!(keys.len(), 2);
490 /// assert!(keys.contains(&"port".to_string()));
491 /// assert!(keys.contains(&"host".to_string()));
492 /// ```
493 pub fn keys(&self) -> Vec<String> {
494 self.properties.keys().cloned().collect()
495 }
496
497 /// Looks up a property by key for internal read paths.
498 ///
499 /// # Parameters
500 ///
501 /// * `name` - Configuration key
502 ///
503 /// # Returns
504 ///
505 /// `Ok(&Property)` if the key exists, or [`ConfigError::PropertyNotFound`]
506 /// otherwise.
507 #[inline]
508 fn get_property_by_name(&self, name: &str) -> ConfigResult<&Property> {
509 self.properties
510 .get(name)
511 .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))
512 }
513
514 /// Ensures the entry for `name` is not marked final before a write.
515 ///
516 /// Missing keys are allowed (writes may create them).
517 ///
518 /// # Parameters
519 ///
520 /// * `name` - Configuration key
521 ///
522 /// # Returns
523 ///
524 /// `Ok(())` if the key is absent or not final, or
525 /// [`ConfigError::PropertyIsFinal`] if an existing property is final.
526 #[inline]
527 fn ensure_property_not_final(&self, name: &str) -> ConfigResult<()> {
528 if let Some(prop) = self.properties.get(name)
529 && prop.is_final()
530 {
531 return Err(ConfigError::PropertyIsFinal(name.to_string()));
532 }
533 Ok(())
534 }
535
536 /// Ensures no property is final before a bulk destructive operation.
537 #[inline]
538 fn ensure_no_final_properties(&self) -> ConfigResult<()> {
539 if let Some((name, _)) = self.properties.iter().find(|(_, prop)| prop.is_final()) {
540 return Err(ConfigError::PropertyIsFinal(name.clone()));
541 }
542 Ok(())
543 }
544
545 /// Shared "optional get" semantics: treat missing or empty properties as
546 /// `None`, otherwise run `read` and wrap the result in `Some`.
547 ///
548 /// Used by [`Self::get_optional`], [`Self::get_optional_list`],
549 /// [`Self::get_optional_string`], and [`Self::get_optional_string_list`].
550 ///
551 /// # Type Parameters
552 ///
553 /// * `T` - Value type produced by `read`
554 ///
555 /// # Parameters
556 ///
557 /// * `name` - Configuration key
558 /// * `read` - Loads `T` when the key exists and is non-empty (typically
559 /// delegates to [`Self::get`], [`Self::get_list`], etc.)
560 ///
561 /// # Returns
562 ///
563 /// `Ok(None)` if the key is missing or the property has no values;
564 /// `Ok(Some(value))` on success; or the error from `read` on failure.
565 fn get_optional_when_present<T>(
566 &self,
567 name: &str,
568 read: impl FnOnce(&Self) -> ConfigResult<T>,
569 ) -> ConfigResult<Option<T>> {
570 match self.properties.get(name) {
571 None => Ok(None),
572 Some(prop) if prop.is_empty() => Ok(None),
573 Some(_) => read(self).map(Some),
574 }
575 }
576
577 // ========================================================================
578 // Core Generic Methods
579 // ========================================================================
580
581 /// Gets a configuration value, converting the stored first value to `T`.
582 ///
583 /// Core read API with type inference.
584 ///
585 /// # Note
586 ///
587 /// This method does not perform variable substitution for string types. If
588 /// you need variable substitution, use [`Self::get_string`].
589 ///
590 /// # Type Parameters
591 ///
592 /// * `T` - Target type supported by [`FromConfig`]
593 ///
594 /// # Parameters
595 ///
596 /// * `name` - Configuration item name
597 ///
598 /// # Returns
599 ///
600 /// The value of the specified type on success, or a [`ConfigError`] on
601 /// failure.
602 ///
603 /// # Errors
604 ///
605 /// - [`ConfigError::PropertyNotFound`] if the key does not exist
606 /// - [`ConfigError::PropertyHasNoValue`] if the property has no value
607 /// - [`ConfigError::ConversionError`] if the stored value cannot be
608 /// converted to `T`
609 ///
610 /// # Examples
611 ///
612 /// ```rust
613 /// use qubit_config::Config;
614 ///
615 /// let mut config = Config::new();
616 /// config.set("port", 8080).unwrap();
617 /// config.set("host", "localhost").unwrap();
618 ///
619 /// // Method 1: Type inference
620 /// let port: i32 = config.get("port").unwrap();
621 /// let host: String = config.get("host").unwrap();
622 ///
623 /// // Method 2: Turbofish
624 /// let port = config.get::<i32>("port").unwrap();
625 /// let host = config.get::<String>("host").unwrap();
626 ///
627 /// // Method 3: Inference from usage
628 /// fn start_server(port: i32, host: String) { }
629 /// start_server(config.get("port").unwrap(), config.get("host").unwrap());
630 /// ```
631 pub fn get<T>(&self, name: &str) -> ConfigResult<T>
632 where
633 T: FromConfig,
634 {
635 <Self as ConfigReader>::get(self, name)
636 }
637
638 /// Gets a configuration value only when the stored value already has the
639 /// exact requested type.
640 ///
641 /// Unlike [`Self::get`], this method preserves the pre-conversion read
642 /// semantics. For example, a stored string `"1"` can be read as `bool` by
643 /// [`Self::get`], but [`Self::get_strict`] returns
644 /// [`ConfigError::TypeMismatch`].
645 ///
646 /// # Type Parameters
647 ///
648 /// * `T` - Exact target type supported by [`MultiValuesFirstGetter`]
649 ///
650 /// # Parameters
651 ///
652 /// * `name` - Configuration item name
653 ///
654 /// # Returns
655 ///
656 /// The exact typed value on success, or a [`ConfigError`] on failure.
657 pub fn get_strict<T>(&self, name: &str) -> ConfigResult<T>
658 where
659 MultiValues: MultiValuesFirstGetter<T>,
660 {
661 let property = self.get_property_by_name(name)?;
662
663 property
664 .get_first::<T>()
665 .map_err(|e| utils::map_value_error(name, e))
666 }
667
668 /// Gets a configuration value or returns a default value.
669 ///
670 /// Returns `default` only if the key is missing or explicitly empty.
671 /// Conversion and substitution errors are returned.
672 ///
673 /// # Type Parameters
674 ///
675 /// * `T` - Target type supported by [`FromConfig`]
676 ///
677 /// # Parameters
678 ///
679 /// * `name` - Configuration item name
680 /// * `default` - Default value
681 ///
682 /// # Returns
683 ///
684 /// Returns the configuration value or default value. Conversion and
685 /// substitution errors are returned instead of being hidden by the default.
686 ///
687 /// # Examples
688 ///
689 /// ```rust
690 /// use qubit_config::Config;
691 ///
692 /// let config = Config::new();
693 ///
694 /// let port: i32 = config.get_or("port", 8080).unwrap();
695 /// let host: String = config.get_or("host", "localhost".to_string()).unwrap();
696 ///
697 /// assert_eq!(port, 8080);
698 /// assert_eq!(host, "localhost");
699 /// ```
700 pub fn get_or<T>(&self, name: &str, default: T) -> ConfigResult<T>
701 where
702 T: FromConfig,
703 {
704 <Self as ConfigReader>::get_or(self, name, default)
705 }
706
707 /// Gets the first configured value from `names`.
708 ///
709 /// # Parameters
710 ///
711 /// * `names` - Candidate keys checked in priority order.
712 ///
713 /// # Returns
714 ///
715 /// Parsed value from the first present and non-empty key.
716 pub fn get_any<T>(&self, names: &[&str]) -> ConfigResult<T>
717 where
718 T: FromConfig,
719 {
720 <Self as ConfigReader>::get_any(self, names)
721 }
722
723 /// Gets an optional value from the first configured key.
724 ///
725 /// # Parameters
726 ///
727 /// * `names` - Candidate keys checked in priority order.
728 ///
729 /// # Returns
730 ///
731 /// `Ok(None)` when all keys are missing or empty.
732 pub fn get_optional_any<T>(&self, names: &[&str]) -> ConfigResult<Option<T>>
733 where
734 T: FromConfig,
735 {
736 <Self as ConfigReader>::get_optional_any(self, names)
737 }
738
739 /// Gets the first configured value from `names`, or `default` when absent.
740 ///
741 /// # Parameters
742 ///
743 /// * `names` - Candidate keys checked in priority order.
744 /// * `default` - Fallback used only when all keys are missing or empty.
745 ///
746 /// # Returns
747 ///
748 /// Parsed value or `default`; conversion errors are returned.
749 pub fn get_any_or<T>(&self, names: &[&str], default: T) -> ConfigResult<T>
750 where
751 T: FromConfig,
752 {
753 <Self as ConfigReader>::get_any_or(self, names, default)
754 }
755
756 /// Gets the first configured value from `names` with explicit read options,
757 /// or `default` when absent.
758 ///
759 /// # Parameters
760 ///
761 /// * `names` - Candidate keys checked in priority order.
762 /// * `default` - Fallback used only when all keys are missing or empty.
763 /// * `read_options` - Parsing options for this read.
764 ///
765 /// # Returns
766 ///
767 /// Parsed value or `default`; conversion errors are returned.
768 pub fn get_any_or_with<T>(
769 &self,
770 names: &[&str],
771 default: T,
772 read_options: &ConfigReadOptions,
773 ) -> ConfigResult<T>
774 where
775 T: FromConfig,
776 {
777 <Self as ConfigReader>::get_any_or_with(self, names, default, read_options)
778 }
779
780 /// Reads a declared configuration field.
781 ///
782 /// # Parameters
783 ///
784 /// * `field` - Field declaration with name, aliases, defaults, and optional
785 /// read options.
786 ///
787 /// # Returns
788 ///
789 /// Parsed field value or default.
790 pub fn read<T>(&self, field: ConfigField<T>) -> ConfigResult<T>
791 where
792 T: FromConfig,
793 {
794 <Self as ConfigReader>::read(self, field)
795 }
796
797 /// Reads an optional declared configuration field.
798 ///
799 /// # Parameters
800 ///
801 /// * `field` - Field declaration.
802 ///
803 /// # Returns
804 ///
805 /// Parsed field value, default, or `None`.
806 pub fn read_optional<T>(&self, field: ConfigField<T>) -> ConfigResult<Option<T>>
807 where
808 T: FromConfig,
809 {
810 <Self as ConfigReader>::read_optional(self, field)
811 }
812
813 /// Gets a list of configuration values, converting each stored element to
814 /// `T`.
815 ///
816 /// Gets all values of a configuration item (multi-value configuration).
817 ///
818 /// # Type Parameters
819 ///
820 /// * `T` - Target type supported by [`FromConfig`]
821 ///
822 /// # Parameters
823 ///
824 /// * `name` - Configuration item name
825 ///
826 /// # Returns
827 ///
828 /// Returns a list of values on success, or an error on failure
829 ///
830 /// # Examples
831 ///
832 /// ```rust
833 /// use qubit_config::Config;
834 ///
835 /// let mut config = Config::new();
836 /// config.set("ports", vec![8080, 8081, 8082]).unwrap();
837 ///
838 /// let ports: Vec<i32> = config.get_list("ports").unwrap();
839 /// assert_eq!(ports, vec![8080, 8081, 8082]);
840 /// ```
841 pub fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
842 where
843 T: FromConfig,
844 {
845 <Self as ConfigReader>::get(self, name)
846 }
847
848 /// Gets all configuration values only when the stored values already have
849 /// the exact requested element type.
850 ///
851 /// Unlike [`Self::get_list`], this method preserves the pre-conversion
852 /// list read semantics. It returns an empty vector for empty properties and
853 /// [`ConfigError::TypeMismatch`] for non-empty values of another stored
854 /// type.
855 ///
856 /// # Type Parameters
857 ///
858 /// * `T` - Exact element type supported by [`MultiValuesGetter`]
859 ///
860 /// # Parameters
861 ///
862 /// * `name` - Configuration item name
863 ///
864 /// # Returns
865 ///
866 /// A vector of exact typed values on success, or a [`ConfigError`] on
867 /// failure.
868 pub fn get_list_strict<T>(&self, name: &str) -> ConfigResult<Vec<T>>
869 where
870 MultiValues: MultiValuesGetter<T>,
871 {
872 let property = self.get_property_by_name(name)?;
873 if property.is_empty() {
874 return Ok(Vec::new());
875 }
876
877 property
878 .get::<T>()
879 .map_err(|e| utils::map_value_error(name, e))
880 }
881
882 /// Sets a configuration value
883 ///
884 /// This is the core method for setting configuration values, supporting
885 /// type inference.
886 ///
887 /// # Type Parameters
888 ///
889 /// * `T` - Element type, automatically inferred from the `values` parameter
890 ///
891 /// # Parameters
892 ///
893 /// * `name` - Configuration item name
894 /// * `values` - Value to store; supports `T`, `Vec<T>`, `&[T]`, and related
895 /// forms accepted by [`MultiValues`] setters
896 ///
897 /// # Returns
898 ///
899 /// Returns Ok(()) on success, or an error on failure
900 ///
901 /// # Errors
902 ///
903 /// - [`ConfigError::PropertyIsFinal`] if the property is marked final
904 ///
905 /// # Examples
906 ///
907 /// ```rust
908 /// use qubit_config::Config;
909 ///
910 /// let mut config = Config::new();
911 ///
912 /// // Set single values (type auto-inference)
913 /// config.set("port", 8080).unwrap(); // T inferred as i32
914 /// config.set("host", "localhost").unwrap();
915 /// // T inferred as String; &str is converted
916 /// config.set("debug", true).unwrap(); // T inferred as bool
917 /// config.set("timeout", 30.5).unwrap(); // T inferred as f64
918 ///
919 /// // Set multiple values (type auto-inference)
920 /// config.set("ports", vec![8080, 8081, 8082]).unwrap(); // T inferred as i32
921 /// config.set("hosts", vec!["host1", "host2"]).unwrap();
922 /// // T inferred as &str (then converted)
923 /// ```
924 pub fn set<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
925 where
926 S: for<'a> MultiValuesSetArg<'a>,
927 <S as MultiValuesSetArg<'static>>::Item: Clone,
928 MultiValues: MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
929 + MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
930 + MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
931 {
932 self.ensure_property_not_final(name)?;
933 let property = self
934 .properties
935 .entry(name.to_string())
936 .or_insert_with(|| Property::new(name));
937
938 property.set(values).map_err(ConfigError::from)
939 }
940
941 /// Adds configuration values
942 ///
943 /// Adds values to an existing configuration item (multi-value properties).
944 ///
945 /// # Type Parameters
946 ///
947 /// * `T` - Element type, automatically inferred from the `values` parameter
948 ///
949 /// # Parameters
950 ///
951 /// * `name` - Configuration item name
952 /// * `values` - Values to append; supports the same forms as [`Self::set`]
953 ///
954 /// # Returns
955 ///
956 /// Returns Ok(()) on success, or an error on failure
957 ///
958 /// # Examples
959 ///
960 /// ```rust
961 /// use qubit_config::Config;
962 ///
963 /// let mut config = Config::new();
964 /// config.set("port", 8080).unwrap(); // Set initial value
965 /// config.add("port", 8081).unwrap(); // Add single value
966 /// config.add("port", vec![8082, 8083]).unwrap(); // Add multiple values
967 /// config.add("port", vec![8084, 8085]).unwrap(); // Add slice
968 ///
969 /// let ports: Vec<i32> = config.get_list("port").unwrap();
970 /// assert_eq!(ports, vec![8080, 8081, 8082, 8083, 8084, 8085]);
971 /// ```
972 pub fn add<S>(&mut self, name: &str, values: S) -> ConfigResult<()>
973 where
974 S: for<'a> MultiValuesAddArg<'a, Item = <S as MultiValuesSetArg<'static>>::Item>
975 + for<'a> MultiValuesSetArg<'a>,
976 <S as MultiValuesSetArg<'static>>::Item: Clone,
977 MultiValues: MultiValuesAdder<<S as MultiValuesSetArg<'static>>::Item>
978 + MultiValuesMultiAdder<<S as MultiValuesSetArg<'static>>::Item>
979 + MultiValuesSetter<<S as MultiValuesSetArg<'static>>::Item>
980 + MultiValuesSetterSlice<<S as MultiValuesSetArg<'static>>::Item>
981 + MultiValuesSingleSetter<<S as MultiValuesSetArg<'static>>::Item>,
982 {
983 self.ensure_property_not_final(name)?;
984
985 if let Some(property) = self.properties.get_mut(name) {
986 property.add(values).map_err(ConfigError::from)
987 } else {
988 let mut property = Property::new(name);
989 // Note: property.set() always returns Ok(()) in current MultiValues implementation,
990 // as it unconditionally replaces the entire value without any validation.
991 // We explicitly ignore the result to improve code coverage and avoid unreachable error paths.
992 let _ = property.set(values);
993 self.properties.insert(name.to_string(), property);
994 Ok(())
995 }
996 }
997
998 // ========================================================================
999 // String Special Handling (Variable Substitution)
1000 // ========================================================================
1001
1002 /// Gets a string configuration value (with variable substitution)
1003 ///
1004 /// If variable substitution is enabled, replaces `${var_name}` placeholders
1005 /// in the stored string.
1006 ///
1007 /// # Parameters
1008 ///
1009 /// * `name` - Configuration item name
1010 ///
1011 /// # Returns
1012 ///
1013 /// Returns the string value on success, or an error on failure
1014 ///
1015 /// # Examples
1016 ///
1017 /// ```rust
1018 /// use qubit_config::Config;
1019 ///
1020 /// let mut config = Config::new();
1021 /// config.set("base_url", "http://localhost").unwrap();
1022 /// config.set("api_url", "${base_url}/api").unwrap();
1023 ///
1024 /// let api_url = config.get_string("api_url").unwrap();
1025 /// assert_eq!(api_url, "http://localhost/api");
1026 /// ```
1027 pub fn get_string(&self, name: &str) -> ConfigResult<String> {
1028 <Self as ConfigReader>::get_string(self, name)
1029 }
1030
1031 /// Gets a string value from the first present and non-empty key in `names`.
1032 ///
1033 /// # Parameters
1034 ///
1035 /// * `names` - Candidate keys checked in priority order.
1036 ///
1037 /// # Returns
1038 ///
1039 /// Returns the string value on success, or an error on failure.
1040 pub fn get_string_any(&self, names: &[&str]) -> ConfigResult<String> {
1041 <Self as ConfigReader>::get_string_any(self, names)
1042 }
1043
1044 /// Gets an optional string value from the first present and non-empty key.
1045 ///
1046 /// # Parameters
1047 ///
1048 /// * `names` - Candidate keys checked in priority order.
1049 ///
1050 /// # Returns
1051 ///
1052 /// `Ok(None)` when all keys are missing or empty.
1053 pub fn get_optional_string_any(&self, names: &[&str]) -> ConfigResult<Option<String>> {
1054 <Self as ConfigReader>::get_optional_string_any(self, names)
1055 }
1056
1057 /// Gets a string from any key, or `default` when all keys are missing or
1058 /// empty.
1059 ///
1060 /// # Parameters
1061 ///
1062 /// * `names` - Candidate keys checked in priority order.
1063 /// * `default` - Fallback used only when all keys are missing or empty.
1064 ///
1065 /// # Returns
1066 ///
1067 /// Returns the string value or default value. Substitution errors are
1068 /// returned instead of being hidden by the default.
1069 pub fn get_string_any_or(&self, names: &[&str], default: &str) -> ConfigResult<String> {
1070 <Self as ConfigReader>::get_string_any_or(self, names, default)
1071 }
1072
1073 /// Gets a string with substitution, or `default` if the key is absent or
1074 /// empty.
1075 ///
1076 /// # Parameters
1077 ///
1078 /// * `name` - Configuration item name
1079 /// * `default` - Default value
1080 ///
1081 /// # Returns
1082 ///
1083 /// Returns the string value or default value. Substitution errors are
1084 /// returned instead of being hidden by the default.
1085 ///
1086 pub fn get_string_or(&self, name: &str, default: &str) -> ConfigResult<String> {
1087 self.get_or(name, default.to_string())
1088 }
1089
1090 /// Gets a list of string configuration values (with variable substitution)
1091 ///
1092 /// If variable substitution is enabled, runs it on each list element
1093 /// (same `${var_name}` rules as [`Self::get_string`]).
1094 ///
1095 /// # Parameters
1096 ///
1097 /// * `name` - Configuration item name
1098 ///
1099 /// # Returns
1100 ///
1101 /// Returns a list of strings on success, or an error on failure
1102 ///
1103 /// # Examples
1104 ///
1105 /// ```rust
1106 /// use qubit_config::Config;
1107 ///
1108 /// let mut config = Config::new();
1109 /// config.set("base_path", "/opt/app").unwrap();
1110 /// config.set("paths", vec!["${base_path}/bin", "${base_path}/lib"]).unwrap();
1111 ///
1112 /// let paths = config.get_string_list("paths").unwrap();
1113 /// assert_eq!(paths, vec!["/opt/app/bin", "/opt/app/lib"]);
1114 /// ```
1115 pub fn get_string_list(&self, name: &str) -> ConfigResult<Vec<String>> {
1116 self.get(name)
1117 }
1118
1119 /// Gets a list of string configuration values or returns a default value
1120 /// (with variable substitution)
1121 ///
1122 /// # Parameters
1123 ///
1124 /// * `name` - Configuration item name
1125 /// * `default` - Default value (can be array slice or vec)
1126 ///
1127 /// # Returns
1128 ///
1129 /// Returns the list of strings or default value. Substitution and parsing
1130 /// errors are returned instead of being hidden by the default.
1131 ///
1132 /// # Examples
1133 ///
1134 /// ```rust
1135 /// use qubit_config::Config;
1136 ///
1137 /// let config = Config::new();
1138 ///
1139 /// // Using array slice
1140 /// let paths = config.get_string_list_or("paths", &["/default/path"]).unwrap();
1141 /// assert_eq!(paths, vec!["/default/path"]);
1142 ///
1143 /// // Using vec
1144 /// let paths = config.get_string_list_or("paths", &vec!["path1", "path2"]).unwrap();
1145 /// assert_eq!(paths, vec!["path1", "path2"]);
1146 /// ```
1147 pub fn get_string_list_or(&self, name: &str, default: &[&str]) -> ConfigResult<Vec<String>> {
1148 self.get_or(name, default.iter().map(|s| s.to_string()).collect())
1149 }
1150
1151 // ========================================================================
1152 // Configuration Source Integration
1153 // ========================================================================
1154
1155 /// Creates a new configuration by loading a [`ConfigSource`].
1156 ///
1157 /// The returned configuration starts empty and is populated by the given
1158 /// source. This is a convenience constructor for callers that do not need
1159 /// to customize the target [`Config`] before loading.
1160 ///
1161 /// # Parameters
1162 ///
1163 /// * `source` - The configuration source to load from.
1164 ///
1165 /// # Returns
1166 ///
1167 /// A populated configuration.
1168 ///
1169 /// # Errors
1170 ///
1171 /// Returns any [`ConfigError`] produced by the source while loading or by
1172 /// the underlying config mutation methods.
1173 #[inline]
1174 pub fn from_source(source: &dyn ConfigSource) -> ConfigResult<Self> {
1175 let mut config = Self::new();
1176 source.load(&mut config)?;
1177 Ok(config)
1178 }
1179
1180 /// Creates a configuration from all current process environment variables.
1181 ///
1182 /// Environment variable names are loaded as-is. Use
1183 /// [`Self::from_env_prefix`] when the application uses a dedicated prefix
1184 /// and wants normalized dot-separated keys.
1185 ///
1186 /// # Returns
1187 ///
1188 /// A configuration populated from the process environment.
1189 ///
1190 /// # Errors
1191 ///
1192 /// Returns [`ConfigError`] if a matching environment key or value is not
1193 /// valid Unicode, or if setting a loaded property fails.
1194 #[inline]
1195 pub fn from_env() -> ConfigResult<Self> {
1196 let source = EnvConfigSource::new();
1197 Self::from_source(&source)
1198 }
1199
1200 /// Creates a configuration from environment variables with a prefix.
1201 ///
1202 /// Only variables starting with `prefix` are loaded. The prefix is stripped,
1203 /// the remaining key is lowercased, and underscores are converted to dots.
1204 ///
1205 /// # Parameters
1206 ///
1207 /// * `prefix` - Prefix used to select environment variables.
1208 ///
1209 /// # Returns
1210 ///
1211 /// A configuration populated from matching environment variables.
1212 ///
1213 /// # Errors
1214 ///
1215 /// Returns [`ConfigError`] if a matching environment key or value is not
1216 /// valid Unicode, or if setting a loaded property fails.
1217 #[inline]
1218 pub fn from_env_prefix(prefix: &str) -> ConfigResult<Self> {
1219 let source = EnvConfigSource::with_prefix(prefix);
1220 Self::from_source(&source)
1221 }
1222
1223 /// Creates a configuration from environment variables with explicit key
1224 /// transformation options.
1225 ///
1226 /// # Parameters
1227 ///
1228 /// * `prefix` - Prefix used to select environment variables.
1229 /// * `strip_prefix` - Whether to strip the prefix from loaded keys.
1230 /// * `convert_underscores` - Whether to convert underscores to dots.
1231 /// * `lowercase_keys` - Whether to lowercase loaded keys.
1232 ///
1233 /// # Returns
1234 ///
1235 /// A configuration populated from matching environment variables.
1236 ///
1237 /// # Errors
1238 ///
1239 /// Returns [`ConfigError`] if a matching environment key or value is not
1240 /// valid Unicode, or if setting a loaded property fails.
1241 #[inline]
1242 pub fn from_env_options(
1243 prefix: &str,
1244 strip_prefix: bool,
1245 convert_underscores: bool,
1246 lowercase_keys: bool,
1247 ) -> ConfigResult<Self> {
1248 let source = EnvConfigSource::with_options(
1249 prefix,
1250 strip_prefix,
1251 convert_underscores,
1252 lowercase_keys,
1253 );
1254 Self::from_source(&source)
1255 }
1256
1257 /// Creates a configuration from a TOML file.
1258 ///
1259 /// # Parameters
1260 ///
1261 /// * `path` - Path to the TOML file.
1262 ///
1263 /// # Returns
1264 ///
1265 /// A configuration populated from the TOML file.
1266 ///
1267 /// # Errors
1268 ///
1269 /// Returns [`ConfigError::IoError`] if the file cannot be read,
1270 /// [`ConfigError::ParseError`] if the TOML cannot be parsed, or another
1271 /// [`ConfigError`] if setting a loaded property fails.
1272 #[inline]
1273 pub fn from_toml_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1274 let source = TomlConfigSource::from_file(path);
1275 Self::from_source(&source)
1276 }
1277
1278 /// Creates a configuration from a YAML file.
1279 ///
1280 /// # Parameters
1281 ///
1282 /// * `path` - Path to the YAML file.
1283 ///
1284 /// # Returns
1285 ///
1286 /// A configuration populated from the YAML file.
1287 ///
1288 /// # Errors
1289 ///
1290 /// Returns [`ConfigError::IoError`] if the file cannot be read,
1291 /// [`ConfigError::ParseError`] if the YAML cannot be parsed, or another
1292 /// [`ConfigError`] if setting a loaded property fails.
1293 #[inline]
1294 pub fn from_yaml_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1295 let source = YamlConfigSource::from_file(path);
1296 Self::from_source(&source)
1297 }
1298
1299 /// Creates a configuration from a Java `.properties` file.
1300 ///
1301 /// # Parameters
1302 ///
1303 /// * `path` - Path to the `.properties` file.
1304 ///
1305 /// # Returns
1306 ///
1307 /// A configuration populated from the `.properties` file.
1308 ///
1309 /// # Errors
1310 ///
1311 /// Returns [`ConfigError::IoError`] if the file cannot be read, or another
1312 /// [`ConfigError`] if setting a loaded property fails.
1313 #[inline]
1314 pub fn from_properties_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1315 let source = PropertiesConfigSource::from_file(path);
1316 Self::from_source(&source)
1317 }
1318
1319 /// Creates a configuration from a `.env` file.
1320 ///
1321 /// # Parameters
1322 ///
1323 /// * `path` - Path to the `.env` file.
1324 ///
1325 /// # Returns
1326 ///
1327 /// A configuration populated from the `.env` file.
1328 ///
1329 /// # Errors
1330 ///
1331 /// Returns [`ConfigError::IoError`] if the file cannot be read,
1332 /// [`ConfigError::ParseError`] if dotenv parsing fails, or another
1333 /// [`ConfigError`] if setting a loaded property fails.
1334 #[inline]
1335 pub fn from_env_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1336 let source = EnvFileConfigSource::from_file(path);
1337 Self::from_source(&source)
1338 }
1339
1340 /// Merges configuration from a `ConfigSource`
1341 ///
1342 /// Loads all key-value pairs from the given source and merges them into
1343 /// this configuration. Existing non-final properties are overwritten;
1344 /// final properties are preserved and cause an error if the source tries
1345 /// to overwrite them.
1346 ///
1347 /// # Parameters
1348 ///
1349 /// * `source` - The configuration source to load from
1350 ///
1351 /// # Returns
1352 ///
1353 /// Returns `Ok(())` on success, or a `ConfigError` on failure
1354 ///
1355 /// # Examples
1356 ///
1357 /// ```rust
1358 /// use qubit_config::Config;
1359 /// use qubit_config::source::{
1360 /// CompositeConfigSource, ConfigSource,
1361 /// EnvConfigSource, TomlConfigSource,
1362 /// };
1363 ///
1364 /// let mut composite = CompositeConfigSource::new();
1365 /// let path = std::env::temp_dir().join(format!(
1366 /// "qubit-config-doc-{}.toml",
1367 /// std::process::id()
1368 /// ));
1369 /// std::fs::write(&path, "app.name = \"demo\"").unwrap();
1370 /// composite.add(TomlConfigSource::from_file(&path));
1371 /// composite.add(EnvConfigSource::with_prefix("APP_"));
1372 ///
1373 /// let mut config = Config::new();
1374 /// config.merge_from_source(&composite).unwrap();
1375 /// std::fs::remove_file(&path).unwrap();
1376 /// ```
1377 #[inline]
1378 pub fn merge_from_source(&mut self, source: &dyn ConfigSource) -> ConfigResult<()> {
1379 source.load(self)
1380 }
1381
1382 // ========================================================================
1383 // Prefix Traversal and Sub-tree Extraction (v0.4.0)
1384 // ========================================================================
1385
1386 /// Iterates over all configuration entries as `(key, &Property)` pairs.
1387 ///
1388 /// # Returns
1389 ///
1390 /// An iterator yielding `(&str, &Property)` tuples.
1391 ///
1392 /// # Examples
1393 ///
1394 /// ```rust
1395 /// use qubit_config::Config;
1396 ///
1397 /// let mut config = Config::new();
1398 /// config.set("host", "localhost").unwrap();
1399 /// config.set("port", 8080).unwrap();
1400 ///
1401 /// for (key, prop) in config.iter() {
1402 /// println!("{} = {:?}", key, prop);
1403 /// }
1404 /// ```
1405 #[inline]
1406 pub fn iter(&self) -> impl Iterator<Item = (&str, &Property)> {
1407 self.properties.iter().map(|(k, v)| (k.as_str(), v))
1408 }
1409
1410 /// Iterates over all configuration entries whose key starts with `prefix`.
1411 ///
1412 /// # Parameters
1413 ///
1414 /// * `prefix` - The key prefix to filter by (e.g., `"http."`)
1415 ///
1416 /// # Returns
1417 ///
1418 /// An iterator of `(&str, &Property)` whose keys start with `prefix`.
1419 ///
1420 /// # Examples
1421 ///
1422 /// ```rust
1423 /// use qubit_config::Config;
1424 ///
1425 /// let mut config = Config::new();
1426 /// config.set("http.host", "localhost").unwrap();
1427 /// config.set("http.port", 8080).unwrap();
1428 /// config.set("db.host", "dbhost").unwrap();
1429 ///
1430 /// let http_entries: Vec<_> = config.iter_prefix("http.").collect();
1431 /// assert_eq!(http_entries.len(), 2);
1432 /// ```
1433 #[inline]
1434 pub fn iter_prefix<'a>(
1435 &'a self,
1436 prefix: &'a str,
1437 ) -> impl Iterator<Item = (&'a str, &'a Property)> {
1438 self.properties
1439 .iter()
1440 .filter(move |(k, _)| k.starts_with(prefix))
1441 .map(|(k, v)| (k.as_str(), v))
1442 }
1443
1444 /// Returns `true` if any configuration key starts with `prefix`.
1445 ///
1446 /// # Parameters
1447 ///
1448 /// * `prefix` - The key prefix to check
1449 ///
1450 /// # Returns
1451 ///
1452 /// `true` if at least one key starts with `prefix`, `false` otherwise.
1453 ///
1454 /// # Examples
1455 ///
1456 /// ```rust
1457 /// use qubit_config::Config;
1458 ///
1459 /// let mut config = Config::new();
1460 /// config.set("http.host", "localhost").unwrap();
1461 ///
1462 /// assert!(config.contains_prefix("http."));
1463 /// assert!(!config.contains_prefix("db."));
1464 /// ```
1465 #[inline]
1466 pub fn contains_prefix(&self, prefix: &str) -> bool {
1467 self.properties.keys().any(|k| k.starts_with(prefix))
1468 }
1469
1470 /// Extracts a sub-configuration for keys matching `prefix`.
1471 ///
1472 /// # Parameters
1473 ///
1474 /// * `prefix` - The key prefix to extract (e.g., `"http"`)
1475 /// * `strip_prefix` - When `true`, removes `prefix` and the following dot
1476 /// from keys in the result; when `false`, keys are copied unchanged.
1477 ///
1478 /// # Returns
1479 ///
1480 /// A new `Config` containing only the matching entries.
1481 ///
1482 /// # Examples
1483 ///
1484 /// ```rust
1485 /// use qubit_config::Config;
1486 ///
1487 /// let mut config = Config::new();
1488 /// config.set("http.host", "localhost").unwrap();
1489 /// config.set("http.port", 8080).unwrap();
1490 /// config.set("db.host", "dbhost").unwrap();
1491 ///
1492 /// let http_config = config.subconfig("http", true).unwrap();
1493 /// assert!(http_config.contains("host"));
1494 /// assert!(http_config.contains("port"));
1495 /// assert!(!http_config.contains("db.host"));
1496 /// ```
1497 pub fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
1498 let mut sub = Config::new();
1499 sub.enable_variable_substitution = self.enable_variable_substitution;
1500 sub.max_substitution_depth = self.max_substitution_depth;
1501
1502 // Empty prefix means "all keys"
1503 if prefix.is_empty() {
1504 for (k, v) in &self.properties {
1505 sub.properties.insert(k.clone(), v.clone());
1506 }
1507 return Ok(sub);
1508 }
1509
1510 let full_prefix = format!("{prefix}.");
1511
1512 for (k, v) in &self.properties {
1513 if k == prefix || k.starts_with(&full_prefix) {
1514 let new_key = if strip_prefix {
1515 if k == prefix {
1516 prefix.to_string()
1517 } else {
1518 k[full_prefix.len()..].to_string()
1519 }
1520 } else {
1521 k.clone()
1522 };
1523 sub.properties.insert(new_key, v.clone());
1524 }
1525 }
1526
1527 Ok(sub)
1528 }
1529
1530 // ========================================================================
1531 // Optional and Null Semantics (v0.4.0)
1532 // ========================================================================
1533
1534 /// Returns `true` if the property exists but has no value (empty / null).
1535 ///
1536 /// This distinguishes between:
1537 /// - Key does not exist → `contains()` returns `false`
1538 /// - Key exists but is empty/null → `is_null()` returns `true`
1539 ///
1540 /// # Parameters
1541 ///
1542 /// * `name` - Configuration item name
1543 ///
1544 /// # Returns
1545 ///
1546 /// `true` if the property exists and has no values (is empty).
1547 ///
1548 /// # Examples
1549 ///
1550 /// ```rust
1551 /// use qubit_config::Config;
1552 /// use qubit_datatype::DataType;
1553 ///
1554 /// let mut config = Config::new();
1555 /// config.set_null("nullable", DataType::String).unwrap();
1556 ///
1557 /// assert!(config.is_null("nullable"));
1558 /// assert!(!config.is_null("missing"));
1559 /// ```
1560 pub fn is_null(&self, name: &str) -> bool {
1561 self.properties
1562 .get(name)
1563 .map(|p| p.is_empty())
1564 .unwrap_or(false)
1565 }
1566
1567 /// Gets an optional configuration value.
1568 ///
1569 /// Distinguishes between three states:
1570 /// - `Ok(Some(value))` – key exists and has a value
1571 /// - `Ok(None)` – key does not exist, **or** exists but is null/empty
1572 /// - `Err(e)` – key exists and has a value, but conversion failed
1573 ///
1574 /// # Type Parameters
1575 ///
1576 /// * `T` - Target type
1577 ///
1578 /// # Parameters
1579 ///
1580 /// * `name` - Configuration item name
1581 ///
1582 /// # Returns
1583 ///
1584 /// `Ok(Some(value))`, `Ok(None)`, or `Err` as described above.
1585 ///
1586 /// # Examples
1587 ///
1588 /// ```rust
1589 /// use qubit_config::Config;
1590 ///
1591 /// let mut config = Config::new();
1592 /// config.set("port", 8080).unwrap();
1593 ///
1594 /// let port: Option<i32> = config.get_optional("port").unwrap();
1595 /// assert_eq!(port, Some(8080));
1596 ///
1597 /// let missing: Option<i32> = config.get_optional("missing").unwrap();
1598 /// assert_eq!(missing, None);
1599 /// ```
1600 pub fn get_optional<T>(&self, name: &str) -> ConfigResult<Option<T>>
1601 where
1602 T: FromConfig,
1603 {
1604 <Self as ConfigReader>::get_optional(self, name)
1605 }
1606
1607 /// Gets an optional list of configuration values.
1608 ///
1609 /// See also [`Self::get_optional_string_list`] for optional string lists
1610 /// with variable substitution.
1611 ///
1612 /// Distinguishes between three states:
1613 /// - `Ok(Some(vec))` – key exists and has values
1614 /// - `Ok(None)` – key does not exist, **or** exists but is null/empty
1615 /// - `Err(e)` – key exists and has values, but conversion failed
1616 ///
1617 /// # Type Parameters
1618 ///
1619 /// * `T` - Target element type supported by [`FromConfig`]
1620 ///
1621 /// # Parameters
1622 ///
1623 /// * `name` - Configuration item name
1624 ///
1625 /// # Returns
1626 ///
1627 /// `Ok(Some(vec))`, `Ok(None)`, or `Err` as described above.
1628 ///
1629 /// # Examples
1630 ///
1631 /// ```rust
1632 /// use qubit_config::Config;
1633 ///
1634 /// let mut config = Config::new();
1635 /// config.set("ports", vec![8080, 8081]).unwrap();
1636 ///
1637 /// let ports: Option<Vec<i32>> = config.get_optional_list("ports").unwrap();
1638 /// assert_eq!(ports, Some(vec![8080, 8081]));
1639 ///
1640 /// let missing: Option<Vec<i32>> = config.get_optional_list("missing").unwrap();
1641 /// assert_eq!(missing, None);
1642 /// ```
1643 pub fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
1644 where
1645 T: FromConfig,
1646 {
1647 <Self as ConfigReader>::get_optional(self, name)
1648 }
1649
1650 /// Gets an optional string (with variable substitution when enabled).
1651 ///
1652 /// Same semantics as [`Self::get_optional`], but values are read via
1653 /// [`Self::get_string`], so `${...}` substitution applies when enabled.
1654 ///
1655 /// # Parameters
1656 ///
1657 /// * `name` - Configuration item name
1658 ///
1659 /// # Returns
1660 ///
1661 /// `Ok(Some(s))`, `Ok(None)`, or `Err` as for [`Self::get_optional`].
1662 ///
1663 /// # Examples
1664 ///
1665 /// ```rust
1666 /// use qubit_config::Config;
1667 ///
1668 /// let mut config = Config::new();
1669 /// config.set("base", "http://localhost").unwrap();
1670 /// config.set("api", "${base}/api").unwrap();
1671 ///
1672 /// let api = config.get_optional_string("api").unwrap();
1673 /// assert_eq!(api.as_deref(), Some("http://localhost/api"));
1674 ///
1675 /// let missing = config.get_optional_string("missing").unwrap();
1676 /// assert_eq!(missing, None);
1677 /// ```
1678 pub fn get_optional_string(&self, name: &str) -> ConfigResult<Option<String>> {
1679 self.get_optional_when_present(name, |c| c.get_string(name))
1680 }
1681
1682 /// Gets an optional string list (substitution per element when enabled).
1683 ///
1684 /// Same semantics as [`Self::get_optional_list`], but elements use
1685 /// [`Self::get_string_list`] (same `${...}` rules as [`Self::get_string`]).
1686 ///
1687 /// # Parameters
1688 ///
1689 /// * `name` - Configuration item name
1690 ///
1691 /// # Returns
1692 ///
1693 /// `Ok(Some(vec))`, `Ok(None)`, or `Err` like [`Self::get_optional_list`].
1694 ///
1695 /// # Examples
1696 ///
1697 /// ```rust
1698 /// use qubit_config::Config;
1699 ///
1700 /// let mut config = Config::new();
1701 /// config.set("root", "/opt/app").unwrap();
1702 /// config.set("paths", vec!["${root}/bin", "${root}/lib"]).unwrap();
1703 ///
1704 /// let paths = config.get_optional_string_list("paths").unwrap();
1705 /// assert_eq!(
1706 /// paths,
1707 /// Some(vec![
1708 /// "/opt/app/bin".to_string(),
1709 /// "/opt/app/lib".to_string(),
1710 /// ]),
1711 /// );
1712 /// ```
1713 pub fn get_optional_string_list(&self, name: &str) -> ConfigResult<Option<Vec<String>>> {
1714 self.get_optional_when_present(name, |c| c.get_string_list(name))
1715 }
1716
1717 // ========================================================================
1718 // Structured Config Deserialization (v0.4.0)
1719 // ========================================================================
1720
1721 /// Deserializes the subtree at `prefix` into `T` using `serde`.
1722 /// String values inside the generated serde value apply the same
1723 /// `${...}` substitution rules as [`Self::get_string`] and
1724 /// [`Self::get_string_list`] when substitution is enabled.
1725 ///
1726 /// Keys under `prefix` (prefix and trailing dot removed) form a flat map
1727 /// for `serde`, for example:
1728 ///
1729 /// ```rust
1730 /// #[derive(serde::Deserialize)]
1731 /// struct HttpOptions {
1732 /// host: String,
1733 /// port: u16,
1734 /// }
1735 /// ```
1736 ///
1737 /// can be populated from config keys `http.host` and `http.port` by calling
1738 /// `config.deserialize::<HttpOptions>("http")`.
1739 ///
1740 /// # Type Parameters
1741 ///
1742 /// * `T` - Target type, must implement `serde::de::DeserializeOwned`
1743 ///
1744 /// # Parameters
1745 ///
1746 /// * `prefix` - Key prefix for the struct fields (`""` means the root map)
1747 ///
1748 /// # Returns
1749 ///
1750 /// The deserialized `T`, or a [`ConfigError::DeserializeError`] on failure.
1751 ///
1752 /// # Examples
1753 ///
1754 /// ```rust
1755 /// use qubit_config::Config;
1756 /// use serde::Deserialize;
1757 ///
1758 /// #[derive(Deserialize, Debug, PartialEq)]
1759 /// struct Server {
1760 /// host: String,
1761 /// port: i32,
1762 /// }
1763 ///
1764 /// let mut config = Config::new();
1765 /// config.set("server.host", "localhost").unwrap();
1766 /// config.set("server.port", 8080).unwrap();
1767 ///
1768 /// let server: Server = config.deserialize("server").unwrap();
1769 /// assert_eq!(server.host, "localhost");
1770 /// assert_eq!(server.port, 8080);
1771 /// ```
1772 pub fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
1773 where
1774 T: DeserializeOwned,
1775 {
1776 let sub = self.subconfig(prefix, true)?;
1777
1778 let mut properties = sub.properties.iter().collect::<Vec<_>>();
1779 properties.sort_by_key(|(left_key, _)| *left_key);
1780
1781 let mut map = Map::new();
1782 for (key, prop) in properties {
1783 let mut json_val = utils::property_to_json_value(prop);
1784 utils::substitute_json_strings_with_fallback(&mut json_val, &sub, self)?;
1785 utils::insert_deserialize_value(&mut map, key, json_val);
1786 }
1787
1788 let json_obj = JsonValue::Object(map);
1789
1790 from_value(json_obj).map_err(|e| ConfigError::DeserializeError {
1791 path: prefix.to_string(),
1792 message: e.to_string(),
1793 })
1794 }
1795
1796 /// Inserts or replaces a property using an explicit [`Property`] object.
1797 ///
1798 /// This method enforces two invariants:
1799 ///
1800 /// - `name` must exactly match `property.name()`
1801 /// - existing final properties cannot be overridden
1802 ///
1803 /// # Parameters
1804 ///
1805 /// * `name` - Target key in this config.
1806 /// * `property` - Property to store under `name`.
1807 ///
1808 /// # Returns
1809 ///
1810 /// `Ok(())` on success.
1811 ///
1812 /// # Errors
1813 ///
1814 /// - [`ConfigError::MergeError`] when `name` and `property.name()` differ.
1815 /// - [`ConfigError::PropertyIsFinal`] when trying to override a final
1816 /// property.
1817 pub fn insert_property(&mut self, name: &str, property: Property) -> ConfigResult<()> {
1818 if property.name() != name {
1819 return Err(ConfigError::MergeError(format!(
1820 "Property name mismatch: key '{name}' != property '{}'",
1821 property.name()
1822 )));
1823 }
1824 self.ensure_property_not_final(name)?;
1825 self.properties.insert(name.to_string(), property);
1826 Ok(())
1827 }
1828
1829 /// Sets a key to a typed null/empty value.
1830 ///
1831 /// This is the preferred public API for representing null/empty values
1832 /// without exposing raw mutable access to the internal map.
1833 ///
1834 /// # Parameters
1835 ///
1836 /// * `name` - Configuration item name.
1837 /// * `data_type` - Data type metadata for the empty value.
1838 ///
1839 /// # Returns
1840 ///
1841 /// `Ok(())` on success.
1842 ///
1843 /// # Errors
1844 ///
1845 /// - [`ConfigError::PropertyIsFinal`] when trying to override a final
1846 /// property.
1847 #[inline]
1848 pub fn set_null(&mut self, name: &str, data_type: DataType) -> ConfigResult<()> {
1849 self.insert_property(
1850 name,
1851 Property::with_value(name, MultiValues::Empty(data_type)),
1852 )
1853 }
1854}
1855
1856impl ConfigReader for Config {
1857 #[inline]
1858 fn is_enable_variable_substitution(&self) -> bool {
1859 Config::is_enable_variable_substitution(self)
1860 }
1861
1862 #[inline]
1863 fn max_substitution_depth(&self) -> usize {
1864 Config::max_substitution_depth(self)
1865 }
1866
1867 #[inline]
1868 fn read_options(&self) -> &ConfigReadOptions {
1869 Config::read_options(self)
1870 }
1871
1872 #[inline]
1873 fn description(&self) -> Option<&str> {
1874 Config::description(self)
1875 }
1876
1877 #[inline]
1878 fn get_property(&self, name: &str) -> Option<&Property> {
1879 Config::get_property(self, name)
1880 }
1881
1882 #[inline]
1883 fn len(&self) -> usize {
1884 Config::len(self)
1885 }
1886
1887 #[inline]
1888 fn is_empty(&self) -> bool {
1889 Config::is_empty(self)
1890 }
1891
1892 #[inline]
1893 fn keys(&self) -> Vec<String> {
1894 Config::keys(self)
1895 }
1896
1897 #[inline]
1898 fn contains(&self, name: &str) -> bool {
1899 Config::contains(self, name)
1900 }
1901
1902 #[inline]
1903 fn get_strict<T>(&self, name: &str) -> ConfigResult<T>
1904 where
1905 MultiValues: MultiValuesFirstGetter<T>,
1906 {
1907 Config::get_strict(self, name)
1908 }
1909
1910 #[inline]
1911 fn get_list<T>(&self, name: &str) -> ConfigResult<Vec<T>>
1912 where
1913 T: FromConfig,
1914 {
1915 Config::get_list(self, name)
1916 }
1917
1918 #[inline]
1919 fn get_list_strict<T>(&self, name: &str) -> ConfigResult<Vec<T>>
1920 where
1921 MultiValues: MultiValuesGetter<T>,
1922 {
1923 Config::get_list_strict(self, name)
1924 }
1925
1926 #[inline]
1927 fn get_optional_list<T>(&self, name: &str) -> ConfigResult<Option<Vec<T>>>
1928 where
1929 T: FromConfig,
1930 {
1931 Config::get_optional_list(self, name)
1932 }
1933
1934 #[inline]
1935 fn contains_prefix(&self, prefix: &str) -> bool {
1936 Config::contains_prefix(self, prefix)
1937 }
1938
1939 #[inline]
1940 fn iter_prefix<'a>(
1941 &'a self,
1942 prefix: &'a str,
1943 ) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
1944 Box::new(Config::iter_prefix(self, prefix))
1945 }
1946
1947 #[inline]
1948 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
1949 Box::new(Config::iter(self))
1950 }
1951
1952 #[inline]
1953 fn is_null(&self, name: &str) -> bool {
1954 Config::is_null(self, name)
1955 }
1956
1957 #[inline]
1958 fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
1959 Config::subconfig(self, prefix, strip_prefix)
1960 }
1961
1962 #[inline]
1963 fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
1964 where
1965 T: DeserializeOwned,
1966 {
1967 Config::deserialize(self, prefix)
1968 }
1969
1970 #[inline]
1971 fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
1972 Config::prefix_view(self, prefix)
1973 }
1974}
1975
1976impl Default for Config {
1977 /// Creates a new default configuration
1978 ///
1979 /// # Returns
1980 ///
1981 /// Returns a new configuration instance
1982 ///
1983 /// # Examples
1984 ///
1985 /// ```rust
1986 /// use qubit_config::Config;
1987 ///
1988 /// let config = Config::default();
1989 /// assert!(config.is_empty());
1990 /// ```
1991 #[inline]
1992 fn default() -> Self {
1993 Self::new()
1994 }
1995}