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
#![allow(clippy::cognitive_complexity)] // See arti#2556
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
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

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

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

            
82
pub use cmdline::CmdLine;
83
pub use err::{ConfigBuildError, ConfigError, ConfigGetValueError, ReconfigureError};
84
pub use flatten::{Flatten, Flattenable};
85
pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
86
pub use listen::*;
87
pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
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
7
    pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
113
        use figment::value::Value as V;
114
7
        let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
115
6
        Ok(match val {
116
4
            V::String(_, s) => s.clone(),
117
2
            V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
118
            _ => format!("{:?}", val),
119
        })
120
7
    }
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
4
    pub fn get_serde_value<T: DeserializeOwned>(
126
4
        &self,
127
4
        key: &str,
128
4
    ) -> Result<Option<T>, ConfigGetValueError> {
129
        use figment::error::{Error as FError, Kind::MissingField};
130
4
        match self.0.extract_inner(key) {
131
1
            Ok(v) => Ok(Some(v)),
132
            Err(FError {
133
                kind: MissingField(..),
134
                ..
135
3
            }) => Ok(None),
136
            Err(e) => Err(into_internal!("Unexpected error looking up config value")(e).into()),
137
        }
138
4
    }
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
9
    pub fn merge_from<T>(&mut self, config: &T) -> Result<(), ConfigError>
150
9
    where
151
9
        T: serde::Serialize,
152
    {
153
9
        let provider = figment::providers::Serialized::from(config, figment::Profile::Default);
154
9
        let mut orig = figment::Figment::new();
155
9
        std::mem::swap(&mut orig, &mut self.0);
156
9
        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
9
        Ok(())
160
9
    }
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
2
    pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
179
2
        match self {
180
            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
181
1
                Err(ReconfigureError::CannotChange {
182
1
                    field: field.as_ref().to_owned(),
183
1
                })
184
            }
185
            Reconfigure::WarnOnFailures => {
186
1
                tracing::warn!("Cannot change {} on a running client.", field.as_ref());
187
1
                Ok(())
188
            }
189
        }
190
2
    }
191

            
192
    /// As `cannot_change`, but return a [`ReconfigureError::CannotChangeToValue`] variant.
193
    ///
194
    /// `manner` should be an adverbial preprositional phrase,
195
    /// like "from on to off" or "while arti is running".
196
    pub fn cannot_change_specific<S, T>(self, field: S, manner: T) -> Result<(), ReconfigureError>
197
    where
198
        S: AsRef<str>,
199
        T: AsRef<str>,
200
    {
201
        match self {
202
            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
203
                Err(ReconfigureError::CannotChangeToValue {
204
                    field: field.as_ref().to_owned(),
205
                    manner: manner.as_ref().to_owned(),
206
                })
207
            }
208
            Reconfigure::WarnOnFailures => {
209
                tracing::warn!(
210
                    "Cannot change {} {} on a running client.",
211
                    field.as_ref(),
212
                    manner.as_ref()
213
                );
214
                Ok(())
215
            }
216
        }
217
    }
218
}
219

            
220
/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`
221
///
222
///  * If the input is `None`, this indicates that the user did not specify a value,
223
///    and we therefore use `def` to obtain the default value.
224
///
225
///  * If the input is `Some(None)`, or `Some(Some(Default::default()))`,
226
///    the user has explicitly specified that this config item should be null/none/nothing,
227
///    so we return `None`.
228
///
229
///  * Otherwise the user provided an actual value, and we return `Some` of it.
230
///
231
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
232
///
233
/// For consistency with other APIs in Arti, when using this,
234
/// do not pass `setter(strip_option)` to derive_builder.
235
///
236
/// # âš  Stability Warning âš 
237
///
238
/// We may significantly change this so that it is an method in an extension trait.
239
//
240
// This is an annoying AOI right now because you have to write things like
241
//     #[builder(field(build = r#"tor_config::resolve_option(&self.dns_port, || None)"#))]
242
//     pub(crate) dns_port: Option<u16>,
243
// which recapitulates the field name.  That is very much a bug hazard (indeed, in an
244
// early version of some of this code I perpetrated precisely that bug).
245
// Fixing this involves a derive_builder feature.
246
276
pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
247
276
where
248
276
    T: Clone + Default + PartialEq,
249
276
    DF: FnOnce() -> Option<T>,
250
{
251
276
    resolve_option_general(
252
276
        input.as_ref().map(|ov| ov.as_ref()),
253
6
        |v| v == &T::default(),
254
276
        def,
255
    )
256
276
}
257

            
258
/// Resolves an `Option<Option<&T>>` (in a builder) into an `Option<T>`, more generally
259
///
260
/// Like [`resolve_option`], but:
261
///
262
///  * Doesn't rely on `T` being `Default + PartialEq`
263
///    to determine whether it's the sentinel value;
264
///    instead, takes `is_sentinel`.
265
///
266
///  * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
267
///
268
/// # Behavior
269
///
270
///  * If the input is `None`, this indicates that the user did not specify a value,
271
///    and we therefore use `def` to obtain the default value.
272
///
273
///  * If the input is `Some(None)`, or `Some(Some(v))` where `is_sentinel(v)` returns true,
274
///    the user has explicitly specified that this config item should be null/none/nothing,
275
///    so we return `None`.
276
///
277
///  * Otherwise the user provided an actual value, and we return `Some` of it.
278
///
279
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
280
///
281
/// # âš  Stability Warning âš 
282
///
283
/// We may significantly change this so that it is an method in an extension trait.
284
///
285
/// # Example
286
/// ```
287
/// use tor_config::resolve_option_general;
288
///
289
/// // Use 0 as a sentinel meaning "explicitly clear" in this example
290
/// let is_sentinel = |v: &i32| *v == 0;
291
///
292
/// // No value provided: use default
293
/// assert_eq!(
294
///     resolve_option_general(None, is_sentinel, || Some(10)),
295
///     Some(10),
296
/// );
297
///
298
/// // Explicitly None
299
/// assert_eq!(
300
///     resolve_option_general(Some(None), is_sentinel, || Some(10)),
301
///     None,
302
/// );
303
///
304
/// // Sentinel value (0) -> return None
305
/// assert_eq!(
306
///     resolve_option_general(Some(Some(&0)), is_sentinel, || Some(10)),
307
///     None,
308
/// );
309
///
310
/// // Set to actual value -> return that value
311
/// assert_eq!(
312
///     resolve_option_general(Some(Some(&5)), is_sentinel, || Some(10)),
313
///     Some(5),
314
/// );
315
/// ```
316
276
pub fn resolve_option_general<T, ISF, DF>(
317
276
    input: Option<Option<&T>>,
318
276
    is_sentinel: ISF,
319
276
    def: DF,
320
276
) -> Option<T>
321
276
where
322
276
    T: Clone,
323
276
    DF: FnOnce() -> Option<T>,
324
276
    ISF: FnOnce(&T) -> bool,
325
{
326
6
    match input {
327
268
        None => def(),
328
2
        Some(None) => None,
329
6
        Some(Some(v)) if is_sentinel(v) => None,
330
2
        Some(Some(v)) => Some(v.clone()),
331
    }
332
276
}
333

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

            
460
        $( // expands iff there was $default, which is always default
461
            impl Default for $Config {
462
33
                fn $default() -> Self {
463
                    // unwrap is good because one of the test cases above checks that it works!
464
33
                    [< $Config Builder >]::default().build().unwrap()
465
33
                }
466
            }
467
        )?
468

            
469
        $( // expands iff there was $Builder, which is always Builder
470
            impl $crate::load::$Builder for [< $Config Builder >] {
471
                type Built = $Config;
472
                fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
473
                    [< $Config Builder >]::build(self)
474
                }
475
            }
476
        )?
477

            
478
        #[test]
479
        #[allow(non_snake_case)]
480
12
        fn [< test_impl_Default_for_ $Config >] () {
481
            #[allow(unused_variables)]
482
12
            let def = None::<$Config>;
483
            $( // expands iff there was $default, which is always default
484
1
                let def = Some($Config::$default());
485
            )?
486

            
487
12
            if let Some(def) = def {
488
1
                $( // expands iff there was $try_deserialize, which is always extract
489
1
                    let empty_config = $crate::deps::figment::Figment::new();
490
1
                    let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
491
1
                    let from_empty = builder.build().unwrap();
492
1
                    assert_eq!(def, from_empty);
493
1
                )*
494
11
            }
495
12
        }
496
    } };
497
}
498

            
499
#[cfg(test)]
500
mod test {
501
    // @@ begin test lint list maintained by maint/add_warning @@
502
    #![allow(clippy::bool_assert_comparison)]
503
    #![allow(clippy::clone_on_copy)]
504
    #![allow(clippy::dbg_macro)]
505
    #![allow(clippy::mixed_attributes_style)]
506
    #![allow(clippy::print_stderr)]
507
    #![allow(clippy::print_stdout)]
508
    #![allow(clippy::single_char_pattern)]
509
    #![allow(clippy::unwrap_used)]
510
    #![allow(clippy::unchecked_time_subtraction)]
511
    #![allow(clippy::useless_vec)]
512
    #![allow(clippy::needless_pass_by_value)]
513
    #![allow(clippy::string_slice)] // See arti#2571
514
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
515
    use super::*;
516
    use crate::{self as tor_config, sources::MustRead};
517
    use derive_builder::Builder;
518
    use serde::{Deserialize, Serialize};
519
    use serde_json::json;
520
    use tracing_test::traced_test;
521

            
522
    #[test]
523
    #[traced_test]
524
    fn reconfigure_helpers() {
525
        let how = Reconfigure::AllOrNothing;
526
        let err = how.cannot_change("the_laws_of_physics").unwrap_err();
527
        assert_eq!(
528
            err.to_string(),
529
            "Cannot change the_laws_of_physics on a running client.".to_owned()
530
        );
531

            
532
        let how = Reconfigure::WarnOnFailures;
533
        let ok = how.cannot_change("stuff");
534
        assert!(ok.is_ok());
535
        assert!(logs_contain("Cannot change stuff on a running client."));
536
    }
537

            
538
    #[test]
539
    #[rustfmt::skip] // autoformatting obscures the regular structure
540
    fn resolve_option_test() {
541
        #[derive(Debug, Clone, Builder, Eq, PartialEq)]
542
        #[builder(build_fn(error = "ConfigBuildError"))]
543
        #[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
544
        struct TestConfig {
545
            #[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
546
            none: Option<u32>,
547

            
548
            #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
549
            four: Option<u32>,
550
        }
551

            
552
        // defaults
553
        {
554
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
555
                json!{ { } }
556
            ).unwrap();
557

            
558
            let builder_from_methods = TestConfigBuilder::default();
559

            
560
            assert_eq!(builder_from_methods, builder_from_json);
561
            assert_eq!(builder_from_methods.build().unwrap(),
562
                        TestConfig { none: None, four: Some(4) });
563
        }
564

            
565
        // explicit positive values
566
        {
567
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
568
                json!{ { "none": 123, "four": 456 } }
569
            ).unwrap();
570

            
571
            let mut builder_from_methods = TestConfigBuilder::default();
572
            builder_from_methods.none(Some(123));
573
            builder_from_methods.four(Some(456));
574

            
575
            assert_eq!(builder_from_methods, builder_from_json);
576
            assert_eq!(builder_from_methods.build().unwrap(),
577
                       TestConfig { none: Some(123), four: Some(456) });
578
        }
579

            
580
        // explicit "null" values
581
        {
582
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
583
                json!{ { "none": 0, "four": 0 } }
584
            ).unwrap();
585

            
586
            let mut builder_from_methods = TestConfigBuilder::default();
587
            builder_from_methods.none(Some(0));
588
            builder_from_methods.four(Some(0));
589

            
590
            assert_eq!(builder_from_methods, builder_from_json);
591
            assert_eq!(builder_from_methods.build().unwrap(),
592
                       TestConfig { none: None, four: None });
593
        }
594

            
595
        // explicit None (API only, serde can't do this for Option)
596
        {
597
            let mut builder_from_methods = TestConfigBuilder::default();
598
            builder_from_methods.none(None);
599
            builder_from_methods.four(None);
600

            
601
            assert_eq!(builder_from_methods.build().unwrap(),
602
                       TestConfig { none: None, four: None });
603
        }
604
    }
605

            
606
    #[test]
607
    fn get_value() {
608
        use serde_value::Value as V;
609
        let to_value = |json_str: &str| {
610
            serde_value::to_value(serde_json::from_str::<serde_json::Value>(json_str).unwrap())
611
                .unwrap()
612
        };
613
        let mut sources = ConfigurationSources::new_empty();
614

            
615
        let source = "
616
        [foo]
617
        bar.baz = 7
618
        quux = [[],[],{}]
619
        ";
620
        let source = ConfigurationSource::from_verbatim(source.to_string());
621
        sources.push_source(source, MustRead::MustRead);
622

            
623
        let tree = sources.load().unwrap();
624

            
625
        {
626
            let v1 = tree.get_serde_value::<V>("foo.quux").unwrap().unwrap();
627
            let v2 = to_value(r#"[[], [], {}]"#);
628
            assert_eq!(v1, v2);
629
        }
630

            
631
        assert!(tree.get_serde_value::<V>("nonexist").unwrap().is_none());
632
        assert!(tree.get_serde_value::<V>("foo.nonexist").unwrap().is_none());
633
        assert!(
634
            tree.get_serde_value::<V>("foo.quux.nonexist")
635
                .unwrap()
636
                .is_none()
637
        );
638
    }
639
}