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