Skip to main content

qubit_config/
config_reader.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
11#![allow(private_bounds)]
12
13use qubit_value::{
14    MultiValues,
15    ValueError,
16};
17use serde::de::DeserializeOwned;
18
19use crate::config_prefix_view::ConfigPrefixView;
20use crate::field::ConfigField;
21use crate::from::{
22    FromConfig,
23    IntoConfigDefault,
24    is_effectively_missing,
25    is_effectively_missing_with_substitution,
26    parse_property_from_reader,
27    parse_property_from_reader_with_substitution,
28};
29use crate::options::ConfigReadOptions;
30use crate::{
31    Config,
32    ConfigError,
33    ConfigName,
34    ConfigNames,
35    ConfigResult,
36    Property,
37};
38
39/// Read-only configuration interface.
40///
41/// This trait allows consumers to read configuration values without requiring
42/// ownership of a [`crate::Config`]. Both [`crate::Config`] and
43/// [`crate::ConfigPrefixView`] implement it.
44///
45/// Its required methods mirror the read-only surface of [`crate::Config`]
46/// (metadata, raw properties, iteration, subtree extraction, and serde
47/// deserialization), with prefix views resolving keys relative to their
48/// logical prefix.
49///
50pub trait ConfigReader {
51    /// Returns whether `${...}` variable substitution is applied when reading
52    /// string values.
53    ///
54    /// # Returns
55    ///
56    /// `true` if substitution is enabled for this reader.
57    fn is_enable_variable_substitution(&self) -> bool;
58
59    /// Returns the maximum recursion depth allowed when resolving nested
60    /// `${...}` references.
61    ///
62    /// # Returns
63    ///
64    /// Maximum substitution depth (see
65    /// `DEFAULT_MAX_SUBSTITUTION_DEPTH` for the default used by
66    /// [`crate::Config`]).
67    fn max_substitution_depth(&self) -> usize;
68
69    /// Returns the optional human-readable description attached to this
70    /// configuration (the whole document; prefix views expose the same value
71    /// as the underlying [`crate::Config`]).
72    fn description(&self) -> Option<&str>;
73
74    /// Returns a reference to the raw [`Property`] for `name`, if present.
75    ///
76    /// For a [`ConfigPrefixView`], `name` is resolved relative to the view
77    /// prefix (same rules as [`Self::get`]).
78    fn get_property(&self, name: impl ConfigName) -> Option<&Property>;
79
80    /// Number of configuration entries visible to this reader (all keys for
81    /// [`crate::Config`]; relative keys only for a [`ConfigPrefixView`]).
82    fn len(&self) -> usize;
83
84    /// Returns `true` when [`Self::len`] is zero.
85    fn is_empty(&self) -> bool;
86
87    /// All keys visible to this reader (relative keys for a prefix view).
88    fn keys(&self) -> Vec<String>;
89
90    /// Returns whether a property exists for the given key.
91    ///
92    /// # Parameters
93    ///
94    /// * `name` - Full configuration key (for [`crate::ConfigPrefixView`],
95    ///   relative keys are resolved against the view prefix).
96    ///
97    /// # Returns
98    ///
99    /// `true` if the key is present.
100    fn contains(&self, name: impl ConfigName) -> bool;
101
102    /// Reads the first stored value for `name` and converts it to `T`.
103    ///
104    /// # Type parameters
105    ///
106    /// * `T` - Target type parsed by [`FromConfig`].
107    ///
108    /// # Parameters
109    ///
110    /// * `name` - Configuration key.
111    ///
112    /// # Returns
113    ///
114    /// The converted value on success, or a [`crate::ConfigError`] if the key
115    /// is missing, empty, or not convertible.
116    fn get<T>(&self, name: impl ConfigName) -> ConfigResult<T>
117    where
118        T: FromConfig,
119    {
120        name.with_config_name(|name| {
121            let resolved = self.resolve_key(name);
122            let property = self
123                .get_property(name)
124                .ok_or_else(|| ConfigError::PropertyNotFound(resolved.clone()))?;
125            if !property.is_empty() && is_effectively_missing(self, &resolved, property, self.read_options())? {
126                return Err(ConfigError::PropertyHasNoValue(resolved));
127            }
128            parse_property_from_reader(self, &resolved, property, self.read_options())
129        })
130    }
131
132    /// Reads the first stored value for `name` without cross-type conversion.
133    ///
134    /// # Type parameters
135    ///
136    /// * `T` - Exact target type; requires `T` to implement
137    ///   `TryFrom<&MultiValues>`.
138    ///
139    /// # Parameters
140    ///
141    /// * `name` - Configuration key.
142    ///
143    /// # Returns
144    ///
145    /// The exact stored value on success, or a [`crate::ConfigError`] if the
146    /// key is missing, empty, or has a different stored type.
147    fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
148    where
149        for<'a> T: TryFrom<&'a MultiValues, Error = ValueError>;
150
151    /// Reads all stored values for `name` and converts each element to `T`.
152    ///
153    /// # Type parameters
154    ///
155    /// * `T` - Element type supported by the shared conversion layer.
156    ///
157    /// # Parameters
158    ///
159    /// * `name` - Configuration key.
160    ///
161    /// # Returns
162    ///
163    /// A vector of values on success, or a [`crate::ConfigError`] on failure.
164    fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
165    where
166        T: FromConfig;
167
168    /// Reads all stored values for `name` without cross-type conversion.
169    ///
170    /// # Type parameters
171    ///
172    /// * `T` - Exact element type; requires `Vec<T>` to implement
173    ///   `TryFrom<&MultiValues>`.
174    ///
175    /// # Parameters
176    ///
177    /// * `name` - Configuration key.
178    ///
179    /// # Returns
180    ///
181    /// A vector of exact stored values on success, or a
182    /// [`crate::ConfigError`] on failure.
183    fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
184    where
185        for<'a> Vec<T>: TryFrom<&'a MultiValues, Error = ValueError>;
186
187    /// Gets a value or `default` if the key is missing or empty.
188    ///
189    /// Conversion and substitution errors are returned instead of being hidden by
190    /// the default.
191    #[inline]
192    fn get_or<T>(&self, name: impl ConfigName, default: impl IntoConfigDefault<T>) -> ConfigResult<T>
193    where
194        T: FromConfig,
195    {
196        self.get_optional(name)
197            .map(|value| value.unwrap_or_else(|| default.into_config_default()))
198    }
199
200    /// Gets an optional value with the same semantics as [`crate::Config::get_optional`].
201    ///
202    /// # Type parameters
203    ///
204    /// * `T` - Target type parsed by [`FromConfig`].
205    ///
206    /// # Parameters
207    ///
208    /// * `name` - Configuration key (relative for a prefix view).
209    ///
210    /// # Returns
211    ///
212    /// `Ok(Some(v))`, `Ok(None)` when missing or empty, or `Err` on conversion failure.
213    fn get_optional<T>(&self, name: impl ConfigName) -> ConfigResult<Option<T>>
214    where
215        T: FromConfig,
216    {
217        name.with_config_name(|name| {
218            let resolved = self.resolve_key(name);
219            match self.get_property(name) {
220                None => Ok(None),
221                Some(property) if is_effectively_missing(self, &resolved, property, self.read_options())? => Ok(None),
222                Some(property) => parse_property_from_reader(self, &resolved, property, self.read_options()).map(Some),
223            }
224        })
225    }
226
227    /// Gets the read options active for this reader.
228    ///
229    /// # Returns
230    ///
231    /// Global read options inherited by field-less reads.
232    fn read_options(&self) -> &ConfigReadOptions;
233
234    /// Reads a value from the first present and non-empty key in `names`.
235    ///
236    /// # Parameters
237    ///
238    /// * `names` - Candidate keys in priority order.
239    ///
240    /// # Returns
241    ///
242    /// Parsed value from the first configured key. Conversion errors stop the
243    /// search and are returned directly.
244    fn get_any<T>(&self, names: impl ConfigNames) -> ConfigResult<T>
245    where
246        T: FromConfig,
247    {
248        names.with_config_names(|names| {
249            self.get_optional_any(names)?
250                .ok_or_else(|| ConfigError::PropertyNotFound(format!("one of: {}", names.join(", "))))
251        })
252    }
253
254    /// Reads an optional value from the first present and non-empty key.
255    ///
256    /// # Parameters
257    ///
258    /// * `names` - Candidate keys in priority order.
259    ///
260    /// # Returns
261    ///
262    /// `Ok(None)` only when all keys are missing or empty.
263    fn get_optional_any<T>(&self, names: impl ConfigNames) -> ConfigResult<Option<T>>
264    where
265        T: FromConfig,
266    {
267        names.with_config_names(|names| self.get_optional_any_with_options(names, self.read_options()))
268    }
269
270    /// Reads a value from any key, using `default` only when all keys are
271    /// absent or empty.
272    ///
273    /// # Parameters
274    ///
275    /// * `names` - Candidate keys in priority order.
276    /// * `default` - Fallback when no candidate is configured.
277    ///
278    /// # Returns
279    ///
280    /// Parsed value or `default`; parsing errors are never swallowed.
281    fn get_any_or<T>(&self, names: impl ConfigNames, default: impl IntoConfigDefault<T>) -> ConfigResult<T>
282    where
283        T: FromConfig,
284    {
285        names.with_config_names(|names| {
286            self.get_optional_any(names)
287                .map(|value| value.unwrap_or_else(|| default.into_config_default()))
288        })
289    }
290
291    /// Reads a value from any key with explicit read options, using `default`
292    /// only when all keys are absent or empty.
293    ///
294    /// # Parameters
295    ///
296    /// * `names` - Candidate keys in priority order.
297    /// * `default` - Fallback when no candidate is configured.
298    /// * `read_options` - Parsing options for this read.
299    ///
300    /// # Returns
301    ///
302    /// Parsed value or `default`; parsing errors are never swallowed.
303    fn get_any_or_with<T>(
304        &self,
305        names: impl ConfigNames,
306        default: impl IntoConfigDefault<T>,
307        read_options: &ConfigReadOptions,
308    ) -> ConfigResult<T>
309    where
310        T: FromConfig,
311    {
312        names.with_config_names(|names| {
313            self.get_optional_any_with_options(names, read_options)
314                .map(|value| value.unwrap_or_else(|| default.into_config_default()))
315        })
316    }
317
318    /// Reads a declared field.
319    ///
320    /// # Parameters
321    ///
322    /// * `field` - Field declaration containing name, aliases, defaults, and
323    ///   optional field-level read options.
324    ///
325    /// # Returns
326    ///
327    /// Parsed field value or its default.
328    fn read<T>(&self, field: ConfigField<T>) -> ConfigResult<T>
329    where
330        T: FromConfig,
331    {
332        let ConfigField {
333            name,
334            aliases,
335            default,
336            read_options,
337        } = field;
338        let options = read_options.as_ref().unwrap_or_else(|| self.read_options());
339        let mut names = Vec::with_capacity(1 + aliases.len());
340        names.push(name.as_str());
341        names.extend(aliases.iter().map(String::as_str));
342        self.get_optional_any_with_options(&names, options)?
343            .or(default)
344            .ok_or_else(|| ConfigError::PropertyNotFound(format!("one of: {}", names.join(", "))))
345    }
346
347    /// Reads an optional declared field.
348    ///
349    /// # Parameters
350    ///
351    /// * `field` - Field declaration.
352    ///
353    /// # Returns
354    ///
355    /// Parsed field value, its default, or `None`.
356    fn read_optional<T>(&self, field: ConfigField<T>) -> ConfigResult<Option<T>>
357    where
358        T: FromConfig,
359    {
360        let ConfigField {
361            name,
362            aliases,
363            default,
364            read_options,
365        } = field;
366        let options = read_options.as_ref().unwrap_or_else(|| self.read_options());
367        let mut names = Vec::with_capacity(1 + aliases.len());
368        names.push(name.as_str());
369        names.extend(aliases.iter().map(String::as_str));
370        self.get_optional_any_with_options(&names, options)
371            .map(|value| value.or(default))
372    }
373
374    /// Shared implementation for field-level and global multi-key reads.
375    fn get_optional_any_with_options<T>(
376        &self,
377        names: impl ConfigNames,
378        options: &ConfigReadOptions,
379    ) -> ConfigResult<Option<T>>
380    where
381        T: FromConfig,
382    {
383        names.with_config_names(|names| {
384            for name in names {
385                let Some(property) = self.get_property(*name) else {
386                    continue;
387                };
388                let resolved = self.resolve_key(*name);
389                if is_effectively_missing(self, &resolved, property, options)? {
390                    continue;
391                }
392                return parse_property_from_reader(self, &resolved, property, options).map(Some);
393            }
394            Ok(None)
395        })
396    }
397
398    /// Gets an optional list with the same semantics as [`crate::Config::get_optional_list`].
399    ///
400    /// # Type parameters
401    ///
402    /// * `T` - Element type supported by the shared conversion layer.
403    ///
404    /// # Parameters
405    ///
406    /// * `name` - Configuration key.
407    ///
408    /// # Returns
409    ///
410    /// `Ok(Some(vec))`, `Ok(None)` when missing or empty, or `Err` on failure.
411    fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
412    where
413        T: FromConfig;
414
415    /// Returns whether any key visible to this reader starts with `prefix`.
416    ///
417    /// # Parameters
418    ///
419    /// * `prefix` - Key prefix to test (for a prefix view, keys are relative to
420    ///   that view).
421    ///
422    /// # Returns
423    ///
424    /// `true` if at least one matching key exists.
425    fn contains_prefix(&self, prefix: &str) -> bool;
426
427    /// Iterates `(key, property)` pairs for keys that start with `prefix`.
428    ///
429    /// # Parameters
430    ///
431    /// * `prefix` - Key prefix filter.
432    ///
433    /// # Returns
434    ///
435    /// A boxed iterator over matching entries.
436    fn iter_prefix<'a>(&'a self, prefix: &'a str) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a>;
437
438    /// Iterates all `(key, property)` pairs visible to this reader (same scope
439    /// as [`Self::keys`]).
440    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a>;
441
442    /// Returns `true` if the key exists and the property has no values (same
443    /// as [`crate::Config::is_null`]).
444    fn is_null(&self, name: impl ConfigName) -> bool;
445
446    /// Extracts a subtree as a new [`Config`] (same semantics as
447    /// [`crate::Config::subconfig`]; on a prefix view, `prefix` is relative to
448    /// the view).
449    fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config>;
450
451    /// Deserializes the subtree at `prefix` with serde (same as
452    /// [`crate::Config::deserialize`]; on a prefix view, `prefix` is relative).
453    fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
454    where
455        T: DeserializeOwned;
456
457    /// Creates a read-only prefix view; relative keys resolve under `prefix`.
458    ///
459    /// Semantics match [`crate::Config::prefix_view`] and
460    /// [`crate::ConfigPrefixView::prefix_view`] (nested prefix when called on a
461    /// view).
462    ///
463    /// # Parameters
464    ///
465    /// * `prefix` - Logical prefix; empty means the full configuration (same as
466    ///   root).
467    ///
468    /// # Returns
469    ///
470    /// A [`ConfigPrefixView`] borrowing this reader's underlying
471    /// [`crate::Config`].
472    fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_>;
473
474    /// Resolves `name` into the canonical key path against the root
475    /// [`crate::Config`].
476    ///
477    /// For a root [`crate::Config`], this returns `name` unchanged. For a
478    /// [`crate::ConfigPrefixView`], this prepends the effective view prefix so
479    /// callers can report root-relative key paths in diagnostics.
480    ///
481    /// # Parameters
482    ///
483    /// * `name` - Relative or absolute key in the current reader scope.
484    ///
485    /// # Returns
486    ///
487    /// Root-relative key path string.
488    #[inline]
489    fn resolve_key(&self, name: impl ConfigName) -> String {
490        name.with_config_name(str::to_string)
491    }
492
493    /// Gets a string value, applying variable substitution when enabled.
494    ///
495    /// # Parameters
496    ///
497    /// * `name` - Configuration key.
498    ///
499    /// # Returns
500    ///
501    /// The string after `${...}` resolution, or a [`crate::ConfigError`].
502    fn get_string(&self, name: impl ConfigName) -> ConfigResult<String> {
503        name.with_config_name(|name| {
504            let resolved = self.resolve_key(name);
505            let property = self
506                .get_property(name)
507                .ok_or_else(|| ConfigError::PropertyNotFound(resolved.clone()))?;
508            if !property.is_empty()
509                && is_effectively_missing_with_substitution(self, &resolved, property, self.read_options())?
510            {
511                return Err(ConfigError::PropertyHasNoValue(resolved));
512            }
513            parse_property_from_reader_with_substitution(self, &resolved, property, self.read_options())
514        })
515    }
516
517    /// Gets a string value from the first present and non-empty key in `names`.
518    ///
519    /// # Parameters
520    ///
521    /// * `names` - Candidate keys in priority order.
522    ///
523    /// # Returns
524    ///
525    /// The resolved string from the first configured key.
526    #[inline]
527    fn get_string_any(&self, names: impl ConfigNames) -> ConfigResult<String> {
528        names.with_config_names(|names| {
529            self.get_optional_string_any(names)?
530                .ok_or_else(|| ConfigError::PropertyNotFound(format!("one of: {}", names.join(", "))))
531        })
532    }
533
534    /// Gets an optional string value from the first present and non-empty key.
535    ///
536    /// # Parameters
537    ///
538    /// * `names` - Candidate keys in priority order.
539    ///
540    /// # Returns
541    ///
542    /// `Ok(None)` only when all keys are missing or empty.
543    #[inline]
544    fn get_optional_string_any(&self, names: impl ConfigNames) -> ConfigResult<Option<String>> {
545        names.with_config_names(|names| self.get_optional_any_with_options_and_substitution(names, self.read_options()))
546    }
547
548    /// Gets a string from any key, or `default` when all keys are missing or
549    /// empty.
550    ///
551    /// # Parameters
552    ///
553    /// * `names` - Candidate keys in priority order.
554    /// * `default` - Fallback string used only when every key is missing or
555    ///   empty.
556    ///
557    /// # Returns
558    ///
559    /// The resolved string or a clone of `default`; substitution errors are
560    /// returned.
561    #[inline]
562    fn get_string_any_or(&self, names: impl ConfigNames, default: &str) -> ConfigResult<String> {
563        names.with_config_names(|names| {
564            self.get_optional_any_with_options_and_substitution(names, self.read_options())
565                .map(|value| value.unwrap_or_else(|| default.to_string()))
566        })
567    }
568
569    /// Gets a string value with substitution, or `default` if the key is
570    /// missing or empty.
571    ///
572    /// # Parameters
573    ///
574    /// * `name` - Configuration key.
575    /// * `default` - Fallback string used only when the key is missing or empty.
576    ///
577    /// # Returns
578    ///
579    /// The resolved string or a clone of `default`; parsing and substitution
580    /// errors are returned.
581    #[inline]
582    fn get_string_or(&self, name: impl ConfigName, default: &str) -> ConfigResult<String> {
583        self.get_optional_string(name)
584            .map(|value| value.unwrap_or_else(|| default.to_string()))
585    }
586
587    /// Gets all string values for `name`, applying substitution to each element
588    /// when enabled.
589    ///
590    /// # Parameters
591    ///
592    /// * `name` - Configuration key.
593    ///
594    /// # Returns
595    ///
596    /// A vector of resolved strings, or a [`crate::ConfigError`].
597    fn get_string_list(&self, name: impl ConfigName) -> ConfigResult<Vec<String>> {
598        name.with_config_name(|name| {
599            let resolved = self.resolve_key(name);
600            let property = self
601                .get_property(name)
602                .ok_or_else(|| ConfigError::PropertyNotFound(resolved.clone()))?;
603            if !property.is_empty()
604                && is_effectively_missing_with_substitution(self, &resolved, property, self.read_options())?
605            {
606                return Err(ConfigError::PropertyHasNoValue(resolved));
607            }
608            parse_property_from_reader_with_substitution(self, &resolved, property, self.read_options())
609        })
610    }
611
612    /// Gets a string list with substitution, or copies `default` if the key is
613    /// missing or empty.
614    ///
615    /// # Parameters
616    ///
617    /// * `name` - Configuration key.
618    /// * `default` - Fallback string slices used only when the key is missing or
619    ///   empty.
620    ///
621    /// # Returns
622    ///
623    /// The resolved list or `default` converted to owned `String`s`; parsing and
624    /// substitution errors are returned.
625    #[inline]
626    fn get_string_list_or(&self, name: impl ConfigName, default: &[&str]) -> ConfigResult<Vec<String>> {
627        self.get_optional_string_list(name)
628            .map(|value| value.unwrap_or_else(|| default.iter().map(|item| (*item).to_string()).collect()))
629    }
630
631    /// Gets an optional string with the same three-way semantics as
632    /// [`crate::Config::get_optional_string`].
633    ///
634    /// # Parameters
635    ///
636    /// * `name` - Configuration key.
637    ///
638    /// # Returns
639    ///
640    /// `Ok(None)` if the key is missing or empty; `Ok(Some(s))` with
641    /// substitution applied; or `Err` if the value exists but cannot be read as
642    /// a string.
643    #[inline]
644    fn get_optional_string(&self, name: impl ConfigName) -> ConfigResult<Option<String>> {
645        name.with_config_name(|name| {
646            let resolved = self.resolve_key(name);
647            match self.get_property(name) {
648                None => Ok(None),
649                Some(property)
650                    if is_effectively_missing_with_substitution(self, &resolved, property, self.read_options())? =>
651                {
652                    Ok(None)
653                }
654                Some(property) => {
655                    parse_property_from_reader_with_substitution(self, &resolved, property, self.read_options())
656                        .map(Some)
657                }
658            }
659        })
660    }
661
662    /// Gets an optional string list with per-element substitution when enabled.
663    ///
664    /// # Parameters
665    ///
666    /// * `name` - Configuration key.
667    ///
668    /// # Returns
669    ///
670    /// `Ok(None)` if the key is missing or empty; `Ok(Some(vec))` otherwise; or
671    /// `Err` on conversion/substitution failure.
672    #[inline]
673    fn get_optional_string_list(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<String>>> {
674        name.with_config_name(|name| {
675            let resolved = self.resolve_key(name);
676            match self.get_property(name) {
677                None => Ok(None),
678                Some(property)
679                    if is_effectively_missing_with_substitution(self, &resolved, property, self.read_options())? =>
680                {
681                    Ok(None)
682                }
683                Some(property) => {
684                    parse_property_from_reader_with_substitution(self, &resolved, property, self.read_options())
685                        .map(Some)
686                }
687            }
688        })
689    }
690
691    /// Shared implementation for string helper multi-key reads.
692    fn get_optional_any_with_options_and_substitution<T>(
693        &self,
694        names: impl ConfigNames,
695        options: &ConfigReadOptions,
696    ) -> ConfigResult<Option<T>>
697    where
698        T: FromConfig,
699    {
700        names.with_config_names(|names| {
701            for name in names {
702                let Some(property) = self.get_property(*name) else {
703                    continue;
704                };
705                let resolved = self.resolve_key(*name);
706                if is_effectively_missing_with_substitution(self, &resolved, property, options)? {
707                    continue;
708                }
709                return parse_property_from_reader_with_substitution(self, &resolved, property, options).map(Some);
710            }
711            Ok(None)
712        })
713    }
714}