Skip to main content

tor_config/
lib.rs

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
49pub mod cmdline;
50pub mod derive;
51mod err;
52#[macro_use]
53pub mod extend_builder;
54pub mod file_watcher;
55mod flatten;
56pub mod list_builder;
57mod listen;
58pub mod load;
59pub mod map_builder;
60mod misc;
61pub mod mistrust;
62mod mut_cfg;
63pub mod setter_traits;
64pub mod sources;
65#[cfg(feature = "testing")]
66pub mod testing;
67
68#[doc(hidden)]
69pub 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
79pub use cmdline::CmdLine;
80pub use err::{ConfigBuildError, ConfigError, ReconfigureError};
81pub use flatten::{Flatten, Flattenable};
82pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
83pub use listen::*;
84pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
85pub use misc::*;
86pub use mut_cfg::MutCfg;
87pub use sources::{ConfigurationSource, ConfigurationSources};
88
89#[doc(hidden)]
90pub use derive_deftly;
91#[doc(hidden)]
92pub use flatten::flattenable_extract_fields;
93
94derive_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)]
102pub struct ConfigurationTree(figment::Figment);
103
104#[cfg(test)]
105impl ConfigurationTree {
106    #[cfg(test)]
107    pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
108        use figment::value::Value as V;
109        let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
110        Ok(match val {
111            V::String(_, s) => s.clone(),
112            V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
113            _ => format!("{:?}", val),
114        })
115    }
116}
117
118/// Rules for reconfiguring a running Arti instance.
119#[derive(Debug, Clone, Copy, Eq, PartialEq)]
120#[non_exhaustive]
121pub 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
130impl 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    pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
134        match self {
135            Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
136                Err(ReconfigureError::CannotChange {
137                    field: field.as_ref().to_owned(),
138                })
139            }
140            Reconfigure::WarnOnFailures => {
141                tracing::warn!("Cannot change {} on a running client.", field.as_ref());
142                Ok(())
143            }
144        }
145    }
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.
174pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
175where
176    T: Clone + Default + PartialEq,
177    DF: FnOnce() -> Option<T>,
178{
179    resolve_option_general(
180        input.as_ref().map(|ov| ov.as_ref()),
181        |v| v == &T::default(),
182        def,
183    )
184}
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/// ```
244pub fn resolve_option_general<T, ISF, DF>(
245    input: Option<Option<&T>>,
246    is_sentinel: ISF,
247    def: DF,
248) -> Option<T>
249where
250    T: Clone,
251    DF: FnOnce() -> Option<T>,
252    ISF: FnOnce(&T) -> bool,
253{
254    match input {
255        None => def(),
256        Some(None) => None,
257        Some(Some(v)) if is_sentinel(v) => None,
258        Some(Some(v)) => Some(v.clone()),
259    }
260}
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]
331macro_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            pub fn builder() -> [< $Config Builder >] {
384                Default::default()
385            }
386        }
387
388        $( // expands iff there was $default, which is always default
389            impl Default for $Config {
390                fn $default() -> Self {
391                    // unwrap is good because one of the test cases above checks that it works!
392                    [< $Config Builder >]::default().build().unwrap()
393                }
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                fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
401                    [< $Config Builder >]::build(self)
402                }
403            }
404        )?
405
406        #[test]
407        #[allow(non_snake_case)]
408        fn [< test_impl_Default_for_ $Config >] () {
409            #[allow(unused_variables)]
410            let def = None::<$Config>;
411            $( // expands iff there was $default, which is always default
412                let def = Some($Config::$default());
413            )?
414
415            if let Some(def) = def {
416                $( // expands iff there was $try_deserialize, which is always extract
417                    let empty_config = $crate::deps::figment::Figment::new();
418                    let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
419                    let from_empty = builder.build().unwrap();
420                    assert_eq!(def, from_empty);
421                )*
422            }
423        }
424    } };
425}
426
427#[cfg(test)]
428mod 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}