1
#![cfg_attr(docsrs, feature(doc_auto_cfg, 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_duration_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
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46

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

            
64
#[doc(hidden)]
65
pub mod deps {
66
    pub use educe;
67
    pub use figment;
68
    pub use itertools::Itertools;
69
    pub use paste::paste;
70
    pub use serde;
71
    pub use serde_value;
72
    pub use tor_basic_utils::macro_first_nonempty;
73
}
74

            
75
pub use cmdline::CmdLine;
76
pub use err::{ConfigBuildError, ConfigError, ReconfigureError};
77
pub use flatten::{Flatten, Flattenable};
78
pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
79
pub use listen::*;
80
pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
81
pub use misc::*;
82
pub use mut_cfg::MutCfg;
83
pub use sources::{ConfigurationSource, ConfigurationSources};
84

            
85
use itertools::Itertools;
86

            
87
#[doc(hidden)]
88
pub use derive_deftly;
89
#[doc(hidden)]
90
pub use flatten::flattenable_extract_fields;
91

            
92
derive_deftly::template_export_semver_check! { "0.12.1" }
93

            
94
/// A set of configuration fields, represented as a set of nested K=V
95
/// mappings.
96
///
97
/// (This is a wrapper for an underlying type provided by the library that
98
/// actually does our configuration.)
99
#[derive(Clone, Debug)]
100
pub struct ConfigurationTree(figment::Figment);
101

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

            
116
/// Rules for reconfiguring a running Arti instance.
117
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
118
#[non_exhaustive]
119
pub enum Reconfigure {
120
    /// Perform no reconfiguration unless we can guarantee that all changes will be successful.
121
    AllOrNothing,
122
    /// Try to reconfigure as much as possible; warn on fields that we cannot reconfigure.
123
    WarnOnFailures,
124
    /// Don't reconfigure anything: Only check whether we can guarantee that all changes will be successful.
125
    CheckAllOrNothing,
126
}
127

            
128
impl Reconfigure {
129
    /// Called when we see a disallowed attempt to change `field`: either give a ReconfigureError,
130
    /// or warn and return `Ok(())`, depending on the value of `self`.
131
4
    pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
132
4
        match self {
133
            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
134
2
                Err(ReconfigureError::CannotChange {
135
2
                    field: field.as_ref().to_owned(),
136
2
                })
137
            }
138
            Reconfigure::WarnOnFailures => {
139
2
                tracing::warn!("Cannot change {} on a running client.", field.as_ref());
140
2
                Ok(())
141
            }
142
        }
143
4
    }
144
}
145

            
146
/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`
147
///
148
///  * If the input is `None`, this indicates that the user did not specify a value,
149
///    and we therefore use `def` to obtain the default value.
150
///
151
///  * If the input is `Some(None)`, or `Some(Some(Default::default()))`,
152
///    the user has explicitly specified that this config item should be null/none/nothing,
153
///    so we return `None`.
154
///
155
///  * Otherwise the user provided an actual value, and we return `Some` of it.
156
///
157
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
158
///
159
/// For consistency with other APIs in Arti, when using this,
160
/// do not pass `setter(strip_option)` to derive_builder.
161
///
162
/// # âš  Stability Warning âš 
163
///
164
/// We may significantly change this so that it is an method in an extension trait.
165
//
166
// This is an annoying AOI right now because you have to write things like
167
//     #[builder(field(build = r#"tor_config::resolve_option(&self.dns_port, || None)"#))]
168
//     pub(crate) dns_port: Option<u16>,
169
// which recapitulates the field name.  That is very much a bug hazard (indeed, in an
170
// early version of some of this code I perpetrated precisely that bug).
171
// Fixing this involves a derive_builder feature.
172
212
pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
173
212
where
174
212
    T: Clone + Default + PartialEq,
175
212
    DF: FnOnce() -> Option<T>,
176
212
{
177
212
    resolve_option_general(
178
212
        input.as_ref().map(|ov| ov.as_ref()),
179
212
        |v| v == &T::default(),
180
212
        def,
181
212
    )
182
212
}
183

            
184
/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`, more generally
185
///
186
/// Like `resolve_option`, but:
187
///
188
///  * Doesn't rely on `T`' being `Default + PartialEq`
189
///    to determine whether it's the sentinel value;
190
///    instead, taking `is_explicit`.
191
///
192
///  * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
193
///
194
///  * If the input is `None`, this indicates that the user did not specify a value,
195
///    and we therefore use `def` to obtain the default value.
196
///
197
///  * If the input is `Some(None)`, or `Some(Some(v)) where is_sentinel(v)`,
198
///    the user has explicitly specified that this config item should be null/none/nothing,
199
///    so we return `None`.
200
///
201
///  * Otherwise the user provided an actual value, and we return `Some` of it.
202
///
203
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
204
///
205
/// # âš  Stability Warning âš 
206
///
207
/// We may significantly change this so that it is an method in an extension trait.
208
//
209
// TODO: it would be nice to have an example here, but right now I'm not sure
210
// what type (or config setting) we could put in an example that would be natural enough
211
// to add clarity.  See
212
//  https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/685#note_2829951
213
212
pub fn resolve_option_general<T, ISF, DF>(
214
212
    input: Option<Option<&T>>,
215
212
    is_sentinel: ISF,
216
212
    def: DF,
217
212
) -> Option<T>
218
212
where
219
212
    T: Clone,
220
212
    DF: FnOnce() -> Option<T>,
221
212
    ISF: FnOnce(&T) -> bool,
222
212
{
223
12
    match input {
224
196
        None => def(),
225
4
        Some(None) => None,
226
12
        Some(Some(v)) if is_sentinel(v) => None,
227
4
        Some(Some(v)) => Some(v.clone()),
228
    }
229
212
}
230

            
231
/// Helper for resolving a config item which can be specified in multiple ways
232
///
233
/// Usable when a single configuration item can be specified
234
/// via multiple (alternative) input fields;
235
/// Each input field which is actually present
236
/// should be converted to the common output type,
237
/// and then passed to this function,
238
/// which will handle consistency checks and defaulting.
239
///
240
/// A common use case is deprecated field name/types.
241
/// In that case, the deprecated field names should be added to the appropriate
242
/// [`load::TopLevel::DEPRECATED_KEYS`].
243
///
244
/// `specified` should be an array (or other iterator) of `(key, Option<value>)`
245
/// where `key` is the field name and
246
/// `value` is that field from the builder,
247
/// converted to the common output type `V`.
248
///
249
/// # Example
250
///
251
/// ```
252
/// use derive_builder::Builder;
253
/// use serde::{Deserialize, Serialize};
254
/// use tor_config::{impl_standard_builder, ConfigBuildError, Listen, resolve_alternative_specs};
255
///
256
/// #[derive(Debug, Clone, Builder, Eq, PartialEq)]
257
/// #[builder(build_fn(error = "ConfigBuildError"))]
258
/// #[builder(derive(Debug, Serialize, Deserialize))]
259
/// #[allow(clippy::option_option)]
260
/// pub struct ProxyConfig {
261
///    /// Addresses to listen on for incoming SOCKS connections.
262
///    #[builder(field(build = r#"self.resolve_socks_port()?"#))]
263
///    pub(crate) socks_listen: Listen,
264
///
265
///    /// Port to listen on (at localhost) for incoming SOCKS
266
///    /// connections.
267
///    #[builder(setter(strip_option), field(type = "Option<Option<u16>>", build = "()"))]
268
///    pub(crate) socks_port: (),
269
/// }
270
/// impl_standard_builder! { ProxyConfig }
271
///
272
/// impl ProxyConfigBuilder {
273
///     fn resolve_socks_port(&self) -> Result<Listen, ConfigBuildError> {
274
///         resolve_alternative_specs(
275
///             [
276
///                 ("socks_listen", self.socks_listen.clone()),
277
///                 ("socks_port", self.socks_port.map(Listen::new_localhost_optional)),
278
///             ],
279
///             || Listen::new_localhost(9150),
280
///         )
281
///     }
282
/// }
283
/// ```
284
//
285
// Testing: this is tested quit exhaustively in the context of the listen/port handling, in
286
// crates/arti/src/cfg.rs.
287
410
pub fn resolve_alternative_specs<V, K>(
288
410
    specified: impl IntoIterator<Item = (K, Option<V>)>,
289
410
    default: impl FnOnce() -> V,
290
410
) -> Result<V, ConfigBuildError>
291
410
where
292
410
    K: Into<String>,
293
410
    V: Eq,
294
410
{
295
410
    Ok(specified
296
410
        .into_iter()
297
820
        .filter_map(|(k, v)| Some((k, v?)))
298
410
        .dedup_by(|(_, v1), (_, v2)| v1 == v2)
299
410
        .at_most_one()
300
410
        .map_err(|several| ConfigBuildError::Inconsistent {
301
24
            fields: several.into_iter().map(|(k, _v)| k.into()).collect_vec(),
302
12
            problem: "conflicting fields, specifying different values".into(),
303
410
        })?
304
398
        .map(|(_k, v)| v)
305
398
        .unwrap_or_else(default))
306
410
}
307

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

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

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

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

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

            
473
#[cfg(test)]
474
mod test {
475
    // @@ begin test lint list maintained by maint/add_warning @@
476
    #![allow(clippy::bool_assert_comparison)]
477
    #![allow(clippy::clone_on_copy)]
478
    #![allow(clippy::dbg_macro)]
479
    #![allow(clippy::mixed_attributes_style)]
480
    #![allow(clippy::print_stderr)]
481
    #![allow(clippy::print_stdout)]
482
    #![allow(clippy::single_char_pattern)]
483
    #![allow(clippy::unwrap_used)]
484
    #![allow(clippy::unchecked_duration_subtraction)]
485
    #![allow(clippy::useless_vec)]
486
    #![allow(clippy::needless_pass_by_value)]
487
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
488
    use super::*;
489
    use crate as tor_config;
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
}