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