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
#![deny(clippy::string_slice)] // See arti#2571
48
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49

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

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

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

            
94
#[doc(hidden)]
95
pub use derive_deftly;
96
#[doc(hidden)]
97
pub use flatten::flattenable_extract_fields;
98

            
99
derive_deftly::template_export_semver_check! { "0.12.1" }
100

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
495
    #[test]
496
    #[traced_test]
497
    fn reconfigure_helpers() {
498
        let how = Reconfigure::AllOrNothing;
499
        let err = how.cannot_change("the_laws_of_physics").unwrap_err();
500
        assert_eq!(
501
            err.to_string(),
502
            "Cannot change the_laws_of_physics on a running client.".to_owned()
503
        );
504

            
505
        let how = Reconfigure::WarnOnFailures;
506
        let ok = how.cannot_change("stuff");
507
        assert!(ok.is_ok());
508
        assert!(logs_contain("Cannot change stuff on a running client."));
509
    }
510

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

            
521
            #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
522
            four: Option<u32>,
523
        }
524

            
525
        // defaults
526
        {
527
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
528
                json!{ { } }
529
            ).unwrap();
530

            
531
            let builder_from_methods = TestConfigBuilder::default();
532

            
533
            assert_eq!(builder_from_methods, builder_from_json);
534
            assert_eq!(builder_from_methods.build().unwrap(),
535
                        TestConfig { none: None, four: Some(4) });
536
        }
537

            
538
        // explicit positive values
539
        {
540
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
541
                json!{ { "none": 123, "four": 456 } }
542
            ).unwrap();
543

            
544
            let mut builder_from_methods = TestConfigBuilder::default();
545
            builder_from_methods.none(Some(123));
546
            builder_from_methods.four(Some(456));
547

            
548
            assert_eq!(builder_from_methods, builder_from_json);
549
            assert_eq!(builder_from_methods.build().unwrap(),
550
                       TestConfig { none: Some(123), four: Some(456) });
551
        }
552

            
553
        // explicit "null" values
554
        {
555
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
556
                json!{ { "none": 0, "four": 0 } }
557
            ).unwrap();
558

            
559
            let mut builder_from_methods = TestConfigBuilder::default();
560
            builder_from_methods.none(Some(0));
561
            builder_from_methods.four(Some(0));
562

            
563
            assert_eq!(builder_from_methods, builder_from_json);
564
            assert_eq!(builder_from_methods.build().unwrap(),
565
                       TestConfig { none: None, four: None });
566
        }
567

            
568
        // explicit None (API only, serde can't do this for Option)
569
        {
570
            let mut builder_from_methods = TestConfigBuilder::default();
571
            builder_from_methods.none(None);
572
            builder_from_methods.four(None);
573

            
574
            assert_eq!(builder_from_methods.build().unwrap(),
575
                       TestConfig { none: None, four: None });
576
        }
577
    }
578

            
579
    #[test]
580
    fn get_value() {
581
        use serde_value::Value as V;
582
        let to_value = |json_str: &str| {
583
            serde_value::to_value(serde_json::from_str::<serde_json::Value>(json_str).unwrap())
584
                .unwrap()
585
        };
586
        let mut sources = ConfigurationSources::new_empty();
587

            
588
        let source = "
589
        [foo]
590
        bar.baz = 7
591
        quux = [[],[],{}]
592
        ";
593
        let source = ConfigurationSource::from_verbatim(source.to_string());
594
        sources.push_source(source, MustRead::MustRead);
595

            
596
        let tree = sources.load().unwrap();
597

            
598
        {
599
            let v1 = tree.get_serde_value::<V>("foo.quux").unwrap().unwrap();
600
            let v2 = to_value(r#"[[], [], {}]"#);
601
            assert_eq!(v1, v2);
602
        }
603

            
604
        assert!(tree.get_serde_value::<V>("nonexist").unwrap().is_none());
605
        assert!(tree.get_serde_value::<V>("foo.nonexist").unwrap().is_none());
606
        assert!(
607
            tree.get_serde_value::<V>("foo.quux.nonexist")
608
                .unwrap()
609
                .is_none()
610
        );
611
    }
612
}