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}