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