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