1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![warn(clippy::cognitive_complexity)]
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_time_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
#![allow(clippy::collapsible_if)] // See arti#2342
46
#![deny(clippy::unused_async)]
47
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48

            
49
pub mod cmdline;
50
pub mod derive;
51
mod err;
52
#[macro_use]
53
pub mod extend_builder;
54
pub mod file_watcher;
55
mod flatten;
56
pub mod list_builder;
57
mod listen;
58
pub mod load;
59
pub mod map_builder;
60
mod misc;
61
pub mod mistrust;
62
mod mut_cfg;
63
pub mod setter_traits;
64
pub mod sources;
65
#[cfg(feature = "testing")]
66
pub mod testing;
67

            
68
#[doc(hidden)]
69
pub mod deps {
70
    pub use educe;
71
    pub use figment;
72
    pub use itertools::Itertools;
73
    pub use paste::paste;
74
    pub use serde;
75
    pub use serde_value;
76
    pub use tor_basic_utils::macro_first_nonempty;
77
}
78

            
79
pub use cmdline::CmdLine;
80
pub use err::{ConfigBuildError, ConfigError, ConfigGetValueError, ReconfigureError};
81
pub use flatten::{Flatten, Flattenable};
82
pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
83
pub use listen::*;
84
pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
85
pub use misc::*;
86
pub use mut_cfg::MutCfg;
87
use serde::de::DeserializeOwned;
88
pub use sources::{ConfigurationSource, ConfigurationSources};
89
use tor_error::into_internal;
90

            
91
#[doc(hidden)]
92
pub use derive_deftly;
93
#[doc(hidden)]
94
pub use flatten::flattenable_extract_fields;
95

            
96
derive_deftly::template_export_semver_check! { "0.12.1" }
97

            
98
/// A set of configuration fields, represented as a set of nested K=V
99
/// mappings.
100
///
101
/// (This is a wrapper for an underlying type provided by the library that
102
/// actually does our configuration.)
103
#[derive(Clone, Debug, Default)]
104
#[must_use] // to prevent errors from merge_from.
105
pub struct ConfigurationTree(figment::Figment);
106

            
107
impl ConfigurationTree {
108
    #[cfg(test)]
109
14
    pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
110
        use figment::value::Value as V;
111
14
        let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
112
12
        Ok(match val {
113
8
            V::String(_, s) => s.clone(),
114
4
            V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
115
            _ => format!("{:?}", val),
116
        })
117
14
    }
118

            
119
    /// Return the value with a given key as some type that implements Deserialize.
120
    ///
121
    /// Return `None` if no such value is set in this tree.
122
8
    pub fn get_serde_value<T: DeserializeOwned>(
123
8
        &self,
124
8
        key: &str,
125
8
    ) -> Result<Option<T>, ConfigGetValueError> {
126
        use figment::error::{Error as FError, Kind::MissingField};
127
8
        match self.0.extract_inner(key) {
128
2
            Ok(v) => Ok(Some(v)),
129
            Err(FError {
130
                kind: MissingField(..),
131
                ..
132
6
            }) => Ok(None),
133
            Err(e) => Err(into_internal!("Unexpected error looking up config value")(e).into()),
134
        }
135
8
    }
136

            
137
    /// Override our current tree with the settings in `config`, returning a new
138
    /// `ConfigurationTree`.
139
    ///
140
    /// `config` must be implement [`Serialize`](serde::Serialize),
141
    /// and must serialize to a map.
142
    ///
143
    /// This operation follows the same as are used when reading
144
    /// multiple configuration files in sequence,
145
    /// where option settings in later files replace earlier ones.
146
    #[allow(clippy::unnecessary_wraps)]
147
    pub fn merge_from<T>(mut self, config: &T) -> Result<Self, ConfigError>
148
    where
149
        T: serde::Serialize,
150
    {
151
        let provider = figment::providers::Serialized::from(config, figment::Profile::Default);
152
        // Figment::merge handles errors by making the type of the figment itself into an error...
153
        self.0 = self.0.merge(provider);
154
        // but we don't want our API to rely on that, so we let this be a Result.
155
        Ok(self)
156
    }
157
}
158

            
159
/// Rules for reconfiguring a running Arti instance.
160
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
161
#[non_exhaustive]
162
pub enum Reconfigure {
163
    /// Perform no reconfiguration unless we can guarantee that all changes will be successful.
164
    AllOrNothing,
165
    /// Try to reconfigure as much as possible; warn on fields that we cannot reconfigure.
166
    WarnOnFailures,
167
    /// Don't reconfigure anything: Only check whether we can guarantee that all changes will be successful.
168
    CheckAllOrNothing,
169
}
170

            
171
impl Reconfigure {
172
    /// Called when we see a disallowed attempt to change `field`: either give a ReconfigureError,
173
    /// or warn and return `Ok(())`, depending on the value of `self`.
174
4
    pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
175
4
        match self {
176
            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
177
2
                Err(ReconfigureError::CannotChange {
178
2
                    field: field.as_ref().to_owned(),
179
2
                })
180
            }
181
            Reconfigure::WarnOnFailures => {
182
2
                tracing::warn!("Cannot change {} on a running client.", field.as_ref());
183
2
                Ok(())
184
            }
185
        }
186
4
    }
187
}
188

            
189
/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`
190
///
191
///  * If the input is `None`, this indicates that the user did not specify a value,
192
///    and we therefore use `def` to obtain the default value.
193
///
194
///  * If the input is `Some(None)`, or `Some(Some(Default::default()))`,
195
///    the user has explicitly specified that this config item should be null/none/nothing,
196
///    so we return `None`.
197
///
198
///  * Otherwise the user provided an actual value, and we return `Some` of it.
199
///
200
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
201
///
202
/// For consistency with other APIs in Arti, when using this,
203
/// do not pass `setter(strip_option)` to derive_builder.
204
///
205
/// # âš  Stability Warning âš 
206
///
207
/// We may significantly change this so that it is an method in an extension trait.
208
//
209
// This is an annoying AOI right now because you have to write things like
210
//     #[builder(field(build = r#"tor_config::resolve_option(&self.dns_port, || None)"#))]
211
//     pub(crate) dns_port: Option<u16>,
212
// which recapitulates the field name.  That is very much a bug hazard (indeed, in an
213
// early version of some of this code I perpetrated precisely that bug).
214
// Fixing this involves a derive_builder feature.
215
398
pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
216
398
where
217
398
    T: Clone + Default + PartialEq,
218
398
    DF: FnOnce() -> Option<T>,
219
{
220
398
    resolve_option_general(
221
398
        input.as_ref().map(|ov| ov.as_ref()),
222
12
        |v| v == &T::default(),
223
398
        def,
224
    )
225
398
}
226

            
227
/// Resolves an `Option<Option<&T>>` (in a builder) into an `Option<T>`, more generally
228
///
229
/// Like [`resolve_option`], but:
230
///
231
///  * Doesn't rely on `T` being `Default + PartialEq`
232
///    to determine whether it's the sentinel value;
233
///    instead, takes `is_sentinel`.
234
///
235
///  * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
236
///
237
/// # Behavior
238
///
239
///  * If the input is `None`, this indicates that the user did not specify a value,
240
///    and we therefore use `def` to obtain the default value.
241
///
242
///  * If the input is `Some(None)`, or `Some(Some(v))` where `is_sentinel(v)` returns true,
243
///    the user has explicitly specified that this config item should be null/none/nothing,
244
///    so we return `None`.
245
///
246
///  * Otherwise the user provided an actual value, and we return `Some` of it.
247
///
248
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
249
///
250
/// # âš  Stability Warning âš 
251
///
252
/// We may significantly change this so that it is an method in an extension trait.
253
///
254
/// # Example
255
/// ```
256
/// use tor_config::resolve_option_general;
257
///
258
/// // Use 0 as a sentinel meaning "explicitly clear" in this example
259
/// let is_sentinel = |v: &i32| *v == 0;
260
///
261
/// // No value provided: use default
262
/// assert_eq!(
263
///     resolve_option_general(None, is_sentinel, || Some(10)),
264
///     Some(10),
265
/// );
266
///
267
/// // Explicitly None
268
/// assert_eq!(
269
///     resolve_option_general(Some(None), is_sentinel, || Some(10)),
270
///     None,
271
/// );
272
///
273
/// // Sentinel value (0) -> return None
274
/// assert_eq!(
275
///     resolve_option_general(Some(Some(&0)), is_sentinel, || Some(10)),
276
///     None,
277
/// );
278
///
279
/// // Set to actual value -> return that value
280
/// assert_eq!(
281
///     resolve_option_general(Some(Some(&5)), is_sentinel, || Some(10)),
282
///     Some(5),
283
/// );
284
/// ```
285
398
pub fn resolve_option_general<T, ISF, DF>(
286
398
    input: Option<Option<&T>>,
287
398
    is_sentinel: ISF,
288
398
    def: DF,
289
398
) -> Option<T>
290
398
where
291
398
    T: Clone,
292
398
    DF: FnOnce() -> Option<T>,
293
398
    ISF: FnOnce(&T) -> bool,
294
{
295
12
    match input {
296
382
        None => def(),
297
4
        Some(None) => None,
298
12
        Some(Some(v)) if is_sentinel(v) => None,
299
4
        Some(Some(v)) => Some(v.clone()),
300
    }
301
398
}
302

            
303
/// Defines standard impls for a struct with a `Builder`, incl `Default`
304
///
305
/// **Use this.**  Do not `#[derive(Builder, Default)]`.  That latter approach would produce
306
/// wrong answers if builder attributes are used to specify non-`Default` default values.
307
///
308
/// # Input syntax
309
///
310
/// ```
311
/// use derive_builder::Builder;
312
/// use serde::{Deserialize, Serialize};
313
/// use tor_config::impl_standard_builder;
314
/// use tor_config::ConfigBuildError;
315
///
316
/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
317
/// #[builder(derive(Serialize, Deserialize, Debug))]
318
/// #[builder(build_fn(error = "ConfigBuildError"))]
319
/// struct SomeConfigStruct { }
320
/// impl_standard_builder! { SomeConfigStruct }
321
///
322
/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
323
/// struct UnusualStruct { }
324
/// impl_standard_builder! { UnusualStruct: !Deserialize + !Builder }
325
/// ```
326
///
327
/// # Requirements
328
///
329
/// `$Config`'s builder must have default values for all the fields,
330
/// or this macro-generated self-test will fail.
331
/// This should be OK for all principal elements of our configuration.
332
///
333
/// `$ConfigBuilder` must have an appropriate `Deserialize` impl.
334
///
335
/// # Options
336
///
337
///  * `!Default` suppresses the `Default` implementation, and the corresponding tests.
338
///    This should be done within Arti's configuration only for sub-structures which
339
///    contain mandatory fields (and are themselves optional).
340
///
341
///  * `!Deserialize` suppresses the test case involving `Builder: Deserialize`.
342
///    This should not be done for structs which are part of Arti's configuration,
343
///    but can be appropriate for other types that use [`derive_builder`].
344
///
345
///  * `!Builder` suppresses the impl of the [`tor_config::load::Builder`](load::Builder) trait
346
///    This will be necessary if the error from the builder is not [`ConfigBuildError`].
347
///
348
/// # Generates
349
///
350
///  * `impl Default for $Config`
351
///  * `impl Builder for $ConfigBuilder`
352
///  * a self-test that the `Default` impl actually works
353
///  * a test that the `Builder` can be deserialized from an empty [`ConfigurationTree`],
354
///    and then built, and that the result is the same as the ordinary default.
355
//
356
// The implementation munches fake "trait bounds" (`: !Deserialize + !Wombat ...`) off the RHS.
357
// We're going to add at least one more option.
358
//
359
// When run with `!Default`, this only generates a `builder` impl and an impl of
360
// the `Resolvable` trait which probably won't be used anywhere.  That may seem
361
// like a poor tradeoff (much fiddly macro code to generate a trivial function in
362
// a handful of call sites).  However, this means that `impl_standard_builder!`
363
// can be used in more places.  That sets a good example: always use the macro.
364
//
365
// That is a good example because we want `impl_standard_builder!` to be
366
// used elsewhere because it generates necessary tests of properties
367
// which might otherwise be violated.  When adding code, people add according to the
368
// patterns they see.
369
//
370
// (We, sadly, don't have a good way to *ensure* use of `impl_standard_builder`.)
371
#[macro_export]
372
macro_rules! impl_standard_builder {
373
    // Convert the input into the "being processed format":
374
    {
375
        $Config:ty $(: $($options:tt)* )?
376
    } => { $crate::impl_standard_builder!{
377
        // ^Being processed format:
378
        @ ( Builder                    )
379
          ( default                    )
380
          ( extract                    ) $Config    :                 $( $( $options    )* )?
381
        //  ~~~~~~~~~~~~~~~              ^^^^^^^    ^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
382
        // present iff not !Builder, !Default
383
        // present iff not !Default
384
        // present iff not !Deserialize  type      always present    options yet to be parsed
385
    } };
386
    // If !Deserialize is the next option, implement it by making $try_deserialize absent
387
    {
388
        @ ( $($Builder        :ident)? )
389
          ( $($default        :ident)? )
390
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )*
391
    } => {  $crate::impl_standard_builder!{
392
        @ ( $($Builder              )? )
393
          ( $($default              )? )
394
          (                            ) $Config    :                    $( $options    )*
395
    } };
396
    // If !Builder is the next option, implement it by making $Builder absent
397
    {
398
        @ ( $($Builder        :ident)? )
399
          ( $($default        :ident)? )
400
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Builder     $( $options:tt )*
401
    } => {  $crate::impl_standard_builder!{
402
        @ (                            )
403
          ( $($default              )? )
404
          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
405
    } };
406
    // If !Default is the next option, implement it by making $default absent
407
    {
408
        @ ( $($Builder        :ident)? )
409
          ( $($default        :ident)? )
410
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Default     $( $options:tt )*
411
    } => {  $crate::impl_standard_builder!{
412
        @ ( $($Builder              )? )
413
          (                            )
414
          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
415
    } };
416
    // Having parsed all options, produce output:
417
    {
418
        @ ( $($Builder        :ident)? )
419
          ( $($default        :ident)? )
420
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)?
421
    } => { $crate::deps::paste!{
422
        impl $Config {
423
            /// Returns a fresh, default, builder
424
63289
            pub fn builder() -> [< $Config Builder >] {
425
63289
                Default::default()
426
63289
            }
427
        }
428

            
429
        $( // expands iff there was $default, which is always default
430
            impl Default for $Config {
431
66
                fn $default() -> Self {
432
                    // unwrap is good because one of the test cases above checks that it works!
433
66
                    [< $Config Builder >]::default().build().unwrap()
434
66
                }
435
            }
436
        )?
437

            
438
        $( // expands iff there was $Builder, which is always Builder
439
            impl $crate::load::$Builder for [< $Config Builder >] {
440
                type Built = $Config;
441
                fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
442
                    [< $Config Builder >]::build(self)
443
                }
444
            }
445
        )?
446

            
447
        #[test]
448
        #[allow(non_snake_case)]
449
24
        fn [< test_impl_Default_for_ $Config >] () {
450
            #[allow(unused_variables)]
451
24
            let def = None::<$Config>;
452
            $( // expands iff there was $default, which is always default
453
2
                let def = Some($Config::$default());
454
            )?
455

            
456
24
            if let Some(def) = def {
457
2
                $( // expands iff there was $try_deserialize, which is always extract
458
2
                    let empty_config = $crate::deps::figment::Figment::new();
459
2
                    let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
460
2
                    let from_empty = builder.build().unwrap();
461
2
                    assert_eq!(def, from_empty);
462
2
                )*
463
23
            }
464
24
        }
465
    } };
466
}
467

            
468
#[cfg(test)]
469
mod test {
470
    // @@ begin test lint list maintained by maint/add_warning @@
471
    #![allow(clippy::bool_assert_comparison)]
472
    #![allow(clippy::clone_on_copy)]
473
    #![allow(clippy::dbg_macro)]
474
    #![allow(clippy::mixed_attributes_style)]
475
    #![allow(clippy::print_stderr)]
476
    #![allow(clippy::print_stdout)]
477
    #![allow(clippy::single_char_pattern)]
478
    #![allow(clippy::unwrap_used)]
479
    #![allow(clippy::unchecked_time_subtraction)]
480
    #![allow(clippy::useless_vec)]
481
    #![allow(clippy::needless_pass_by_value)]
482
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
483
    use super::*;
484
    use crate::{self as tor_config, sources::MustRead};
485
    use derive_builder::Builder;
486
    use serde::{Deserialize, Serialize};
487
    use serde_json::json;
488
    use tracing_test::traced_test;
489

            
490
    #[test]
491
    #[traced_test]
492
    fn reconfigure_helpers() {
493
        let how = Reconfigure::AllOrNothing;
494
        let err = how.cannot_change("the_laws_of_physics").unwrap_err();
495
        assert_eq!(
496
            err.to_string(),
497
            "Cannot change the_laws_of_physics on a running client.".to_owned()
498
        );
499

            
500
        let how = Reconfigure::WarnOnFailures;
501
        let ok = how.cannot_change("stuff");
502
        assert!(ok.is_ok());
503
        assert!(logs_contain("Cannot change stuff on a running client."));
504
    }
505

            
506
    #[test]
507
    #[rustfmt::skip] // autoformatting obscures the regular structure
508
    fn resolve_option_test() {
509
        #[derive(Debug, Clone, Builder, Eq, PartialEq)]
510
        #[builder(build_fn(error = "ConfigBuildError"))]
511
        #[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
512
        struct TestConfig {
513
            #[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
514
            none: Option<u32>,
515

            
516
            #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
517
            four: Option<u32>,
518
        }
519

            
520
        // defaults
521
        {
522
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
523
                json!{ { } }
524
            ).unwrap();
525

            
526
            let builder_from_methods = TestConfigBuilder::default();
527

            
528
            assert_eq!(builder_from_methods, builder_from_json);
529
            assert_eq!(builder_from_methods.build().unwrap(),
530
                        TestConfig { none: None, four: Some(4) });
531
        }
532

            
533
        // explicit positive values
534
        {
535
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
536
                json!{ { "none": 123, "four": 456 } }
537
            ).unwrap();
538

            
539
            let mut builder_from_methods = TestConfigBuilder::default();
540
            builder_from_methods.none(Some(123));
541
            builder_from_methods.four(Some(456));
542

            
543
            assert_eq!(builder_from_methods, builder_from_json);
544
            assert_eq!(builder_from_methods.build().unwrap(),
545
                       TestConfig { none: Some(123), four: Some(456) });
546
        }
547

            
548
        // explicit "null" values
549
        {
550
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
551
                json!{ { "none": 0, "four": 0 } }
552
            ).unwrap();
553

            
554
            let mut builder_from_methods = TestConfigBuilder::default();
555
            builder_from_methods.none(Some(0));
556
            builder_from_methods.four(Some(0));
557

            
558
            assert_eq!(builder_from_methods, builder_from_json);
559
            assert_eq!(builder_from_methods.build().unwrap(),
560
                       TestConfig { none: None, four: None });
561
        }
562

            
563
        // explicit None (API only, serde can't do this for Option)
564
        {
565
            let mut builder_from_methods = TestConfigBuilder::default();
566
            builder_from_methods.none(None);
567
            builder_from_methods.four(None);
568

            
569
            assert_eq!(builder_from_methods.build().unwrap(),
570
                       TestConfig { none: None, four: None });
571
        }
572
    }
573

            
574
    #[test]
575
    fn get_value() {
576
        use serde_value::Value as V;
577
        let to_value = |json_str: &str| {
578
            serde_value::to_value(serde_json::from_str::<serde_json::Value>(json_str).unwrap())
579
                .unwrap()
580
        };
581
        let mut sources = ConfigurationSources::new_empty();
582

            
583
        let source = "
584
        [foo]
585
        bar.baz = 7
586
        quux = [[],[],{}]
587
        ";
588
        let source = ConfigurationSource::from_verbatim(source.to_string());
589
        sources.push_source(source, MustRead::MustRead);
590

            
591
        let tree = sources.load().unwrap();
592

            
593
        {
594
            let v1 = tree.get_serde_value::<V>("foo.quux").unwrap().unwrap();
595
            let v2 = to_value(r#"[[], [], {}]"#);
596
            assert_eq!(v1, v2);
597
        }
598

            
599
        assert!(tree.get_serde_value::<V>("nonexist").unwrap().is_none());
600
        assert!(tree.get_serde_value::<V>("foo.nonexist").unwrap().is_none());
601
        assert!(
602
            tree.get_serde_value::<V>("foo.quux.nonexist")
603
                .unwrap()
604
                .is_none()
605
        );
606
    }
607
}