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::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}