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, 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
pub use sources::{ConfigurationSource, ConfigurationSources};
88

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

            
94
derive_deftly::template_export_semver_check! { "0.12.1" }
95

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

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

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

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

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

            
186
/// Resolves an `Option<Option<&T>>` (in a builder) into an `Option<T>`, more generally
187
///
188
/// Like [`resolve_option`], but:
189
///
190
///  * Doesn't rely on `T` being `Default + PartialEq`
191
///    to determine whether it's the sentinel value;
192
///    instead, takes `is_sentinel`.
193
///
194
///  * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
195
///
196
/// # Behavior
197
///
198
///  * If the input is `None`, this indicates that the user did not specify a value,
199
///    and we therefore use `def` to obtain the default value.
200
///
201
///  * If the input is `Some(None)`, or `Some(Some(v))` where `is_sentinel(v)` returns true,
202
///    the user has explicitly specified that this config item should be null/none/nothing,
203
///    so we return `None`.
204
///
205
///  * Otherwise the user provided an actual value, and we return `Some` of it.
206
///
207
/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
208
///
209
/// # âš  Stability Warning âš 
210
///
211
/// We may significantly change this so that it is an method in an extension trait.
212
///
213
/// # Example
214
/// ```
215
/// use tor_config::resolve_option_general;
216
///
217
/// // Use 0 as a sentinel meaning "explicitly clear" in this example
218
/// let is_sentinel = |v: &i32| *v == 0;
219
///
220
/// // No value provided: use default
221
/// assert_eq!(
222
///     resolve_option_general(None, is_sentinel, || Some(10)),
223
///     Some(10),
224
/// );
225
///
226
/// // Explicitly None
227
/// assert_eq!(
228
///     resolve_option_general(Some(None), is_sentinel, || Some(10)),
229
///     None,
230
/// );
231
///
232
/// // Sentinel value (0) -> return None
233
/// assert_eq!(
234
///     resolve_option_general(Some(Some(&0)), is_sentinel, || Some(10)),
235
///     None,
236
/// );
237
///
238
/// // Set to actual value -> return that value
239
/// assert_eq!(
240
///     resolve_option_general(Some(Some(&5)), is_sentinel, || Some(10)),
241
///     Some(5),
242
/// );
243
/// ```
244
398
pub fn resolve_option_general<T, ISF, DF>(
245
398
    input: Option<Option<&T>>,
246
398
    is_sentinel: ISF,
247
398
    def: DF,
248
398
) -> Option<T>
249
398
where
250
398
    T: Clone,
251
398
    DF: FnOnce() -> Option<T>,
252
398
    ISF: FnOnce(&T) -> bool,
253
{
254
12
    match input {
255
382
        None => def(),
256
4
        Some(None) => None,
257
12
        Some(Some(v)) if is_sentinel(v) => None,
258
4
        Some(Some(v)) => Some(v.clone()),
259
    }
260
398
}
261

            
262
/// Defines standard impls for a struct with a `Builder`, incl `Default`
263
///
264
/// **Use this.**  Do not `#[derive(Builder, Default)]`.  That latter approach would produce
265
/// wrong answers if builder attributes are used to specify non-`Default` default values.
266
///
267
/// # Input syntax
268
///
269
/// ```
270
/// use derive_builder::Builder;
271
/// use serde::{Deserialize, Serialize};
272
/// use tor_config::impl_standard_builder;
273
/// use tor_config::ConfigBuildError;
274
///
275
/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
276
/// #[builder(derive(Serialize, Deserialize, Debug))]
277
/// #[builder(build_fn(error = "ConfigBuildError"))]
278
/// struct SomeConfigStruct { }
279
/// impl_standard_builder! { SomeConfigStruct }
280
///
281
/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
282
/// struct UnusualStruct { }
283
/// impl_standard_builder! { UnusualStruct: !Deserialize + !Builder }
284
/// ```
285
///
286
/// # Requirements
287
///
288
/// `$Config`'s builder must have default values for all the fields,
289
/// or this macro-generated self-test will fail.
290
/// This should be OK for all principal elements of our configuration.
291
///
292
/// `$ConfigBuilder` must have an appropriate `Deserialize` impl.
293
///
294
/// # Options
295
///
296
///  * `!Default` suppresses the `Default` implementation, and the corresponding tests.
297
///    This should be done within Arti's configuration only for sub-structures which
298
///    contain mandatory fields (and are themselves optional).
299
///
300
///  * `!Deserialize` suppresses the test case involving `Builder: Deserialize`.
301
///    This should not be done for structs which are part of Arti's configuration,
302
///    but can be appropriate for other types that use [`derive_builder`].
303
///
304
///  * `!Builder` suppresses the impl of the [`tor_config::load::Builder`](load::Builder) trait
305
///    This will be necessary if the error from the builder is not [`ConfigBuildError`].
306
///
307
/// # Generates
308
///
309
///  * `impl Default for $Config`
310
///  * `impl Builder for $ConfigBuilder`
311
///  * a self-test that the `Default` impl actually works
312
///  * a test that the `Builder` can be deserialized from an empty [`ConfigurationTree`],
313
///    and then built, and that the result is the same as the ordinary default.
314
//
315
// The implementation munches fake "trait bounds" (`: !Deserialize + !Wombat ...`) off the RHS.
316
// We're going to add at least one more option.
317
//
318
// When run with `!Default`, this only generates a `builder` impl and an impl of
319
// the `Resolvable` trait which probably won't be used anywhere.  That may seem
320
// like a poor tradeoff (much fiddly macro code to generate a trivial function in
321
// a handful of call sites).  However, this means that `impl_standard_builder!`
322
// can be used in more places.  That sets a good example: always use the macro.
323
//
324
// That is a good example because we want `impl_standard_builder!` to be
325
// used elsewhere because it generates necessary tests of properties
326
// which might otherwise be violated.  When adding code, people add according to the
327
// patterns they see.
328
//
329
// (We, sadly, don't have a good way to *ensure* use of `impl_standard_builder`.)
330
#[macro_export]
331
macro_rules! impl_standard_builder {
332
    // Convert the input into the "being processed format":
333
    {
334
        $Config:ty $(: $($options:tt)* )?
335
    } => { $crate::impl_standard_builder!{
336
        // ^Being processed format:
337
        @ ( Builder                    )
338
          ( default                    )
339
          ( extract                    ) $Config    :                 $( $( $options    )* )?
340
        //  ~~~~~~~~~~~~~~~              ^^^^^^^    ^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
341
        // present iff not !Builder, !Default
342
        // present iff not !Default
343
        // present iff not !Deserialize  type      always present    options yet to be parsed
344
    } };
345
    // If !Deserialize is the next option, implement it by making $try_deserialize absent
346
    {
347
        @ ( $($Builder        :ident)? )
348
          ( $($default        :ident)? )
349
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )*
350
    } => {  $crate::impl_standard_builder!{
351
        @ ( $($Builder              )? )
352
          ( $($default              )? )
353
          (                            ) $Config    :                    $( $options    )*
354
    } };
355
    // If !Builder is the next option, implement it by making $Builder absent
356
    {
357
        @ ( $($Builder        :ident)? )
358
          ( $($default        :ident)? )
359
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Builder     $( $options:tt )*
360
    } => {  $crate::impl_standard_builder!{
361
        @ (                            )
362
          ( $($default              )? )
363
          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
364
    } };
365
    // If !Default is the next option, implement it by making $default absent
366
    {
367
        @ ( $($Builder        :ident)? )
368
          ( $($default        :ident)? )
369
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Default     $( $options:tt )*
370
    } => {  $crate::impl_standard_builder!{
371
        @ ( $($Builder              )? )
372
          (                            )
373
          ( $($try_deserialize      )? ) $Config    :                    $( $options    )*
374
    } };
375
    // Having parsed all options, produce output:
376
    {
377
        @ ( $($Builder        :ident)? )
378
          ( $($default        :ident)? )
379
          ( $($try_deserialize:ident)? ) $Config:ty : $(+)?
380
    } => { $crate::deps::paste!{
381
        impl $Config {
382
            /// Returns a fresh, default, builder
383
56576
            pub fn builder() -> [< $Config Builder >] {
384
56576
                Default::default()
385
56576
            }
386
        }
387

            
388
        $( // expands iff there was $default, which is always default
389
            impl Default for $Config {
390
72
                fn $default() -> Self {
391
                    // unwrap is good because one of the test cases above checks that it works!
392
72
                    [< $Config Builder >]::default().build().unwrap()
393
72
                }
394
            }
395
        )?
396

            
397
        $( // expands iff there was $Builder, which is always Builder
398
            impl $crate::load::$Builder for [< $Config Builder >] {
399
                type Built = $Config;
400
16
                fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
401
16
                    [< $Config Builder >]::build(self)
402
16
                }
403
            }
404
        )?
405

            
406
        #[test]
407
        #[allow(non_snake_case)]
408
32
        fn [< test_impl_Default_for_ $Config >] () {
409
            #[allow(unused_variables)]
410
32
            let def = None::<$Config>;
411
            $( // expands iff there was $default, which is always default
412
8
                let def = Some($Config::$default());
413
            )?
414

            
415
32
            if let Some(def) = def {
416
2
                $( // expands iff there was $try_deserialize, which is always extract
417
8
                    let empty_config = $crate::deps::figment::Figment::new();
418
8
                    let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
419
8
                    let from_empty = builder.build().unwrap();
420
8
                    assert_eq!(def, from_empty);
421
2
                )*
422
25
            }
423
32
        }
424
    } };
425
}
426

            
427
#[cfg(test)]
428
mod test {
429
    // @@ begin test lint list maintained by maint/add_warning @@
430
    #![allow(clippy::bool_assert_comparison)]
431
    #![allow(clippy::clone_on_copy)]
432
    #![allow(clippy::dbg_macro)]
433
    #![allow(clippy::mixed_attributes_style)]
434
    #![allow(clippy::print_stderr)]
435
    #![allow(clippy::print_stdout)]
436
    #![allow(clippy::single_char_pattern)]
437
    #![allow(clippy::unwrap_used)]
438
    #![allow(clippy::unchecked_time_subtraction)]
439
    #![allow(clippy::useless_vec)]
440
    #![allow(clippy::needless_pass_by_value)]
441
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
442
    use super::*;
443
    use crate as tor_config;
444
    use derive_builder::Builder;
445
    use serde::{Deserialize, Serialize};
446
    use serde_json::json;
447
    use tracing_test::traced_test;
448

            
449
    #[test]
450
    #[traced_test]
451
    fn reconfigure_helpers() {
452
        let how = Reconfigure::AllOrNothing;
453
        let err = how.cannot_change("the_laws_of_physics").unwrap_err();
454
        assert_eq!(
455
            err.to_string(),
456
            "Cannot change the_laws_of_physics on a running client.".to_owned()
457
        );
458

            
459
        let how = Reconfigure::WarnOnFailures;
460
        let ok = how.cannot_change("stuff");
461
        assert!(ok.is_ok());
462
        assert!(logs_contain("Cannot change stuff on a running client."));
463
    }
464

            
465
    #[test]
466
    #[rustfmt::skip] // autoformatting obscures the regular structure
467
    fn resolve_option_test() {
468
        #[derive(Debug, Clone, Builder, Eq, PartialEq)]
469
        #[builder(build_fn(error = "ConfigBuildError"))]
470
        #[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
471
        struct TestConfig {
472
            #[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
473
            none: Option<u32>,
474

            
475
            #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
476
            four: Option<u32>,
477
        }
478

            
479
        // defaults
480
        {
481
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
482
                json!{ { } }
483
            ).unwrap();
484

            
485
            let builder_from_methods = TestConfigBuilder::default();
486

            
487
            assert_eq!(builder_from_methods, builder_from_json);
488
            assert_eq!(builder_from_methods.build().unwrap(),
489
                        TestConfig { none: None, four: Some(4) });
490
        }
491

            
492
        // explicit positive values
493
        {
494
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
495
                json!{ { "none": 123, "four": 456 } }
496
            ).unwrap();
497

            
498
            let mut builder_from_methods = TestConfigBuilder::default();
499
            builder_from_methods.none(Some(123));
500
            builder_from_methods.four(Some(456));
501

            
502
            assert_eq!(builder_from_methods, builder_from_json);
503
            assert_eq!(builder_from_methods.build().unwrap(),
504
                       TestConfig { none: Some(123), four: Some(456) });
505
        }
506

            
507
        // explicit "null" values
508
        {
509
            let builder_from_json: TestConfigBuilder = serde_json::from_value(
510
                json!{ { "none": 0, "four": 0 } }
511
            ).unwrap();
512

            
513
            let mut builder_from_methods = TestConfigBuilder::default();
514
            builder_from_methods.none(Some(0));
515
            builder_from_methods.four(Some(0));
516

            
517
            assert_eq!(builder_from_methods, builder_from_json);
518
            assert_eq!(builder_from_methods.build().unwrap(),
519
                       TestConfig { none: None, four: None });
520
        }
521

            
522
        // explicit None (API only, serde can't do this for Option)
523
        {
524
            let mut builder_from_methods = TestConfigBuilder::default();
525
            builder_from_methods.none(None);
526
            builder_from_methods.four(None);
527

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