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#![deny(clippy::string_slice)] // See arti#2571
48//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49
50pub mod cmdline;
51pub mod derive;
52mod err;
53#[macro_use]
54pub mod extend_builder;
55pub mod file_watcher;
56mod flatten;
57pub mod list_builder;
58mod listen;
59pub mod load;
60pub mod map_builder;
61pub mod metrics;
62mod misc;
63pub mod mistrust;
64mod mut_cfg;
65pub mod setter_traits;
66pub mod sources;
67#[cfg(feature = "testing")]
68pub mod testing;
69
70#[doc(hidden)]
71pub mod deps {
72 pub use educe;
73 pub use figment;
74 pub use itertools::Itertools;
75 pub use paste::paste;
76 pub use serde;
77 pub use serde_value;
78 pub use tor_basic_utils::{if_empty, macro_first_nonempty};
79}
80
81pub use cmdline::CmdLine;
82pub use err::{ConfigBuildError, ConfigError, ConfigGetValueError, ReconfigureError};
83pub use flatten::{Flatten, Flattenable};
84pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
85pub use listen::*;
86pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
87pub use metrics::*;
88pub use misc::*;
89pub use mut_cfg::MutCfg;
90use serde::de::DeserializeOwned;
91pub use sources::{ConfigurationSource, ConfigurationSources};
92use tor_error::into_internal;
93
94#[doc(hidden)]
95pub use derive_deftly;
96#[doc(hidden)]
97pub use flatten::flattenable_extract_fields;
98
99derive_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.
108pub struct ConfigurationTree(figment::Figment);
109
110impl ConfigurationTree {
111 #[cfg(test)]
112 pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
113 use figment::value::Value as V;
114 let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
115 Ok(match val {
116 V::String(_, s) => s.clone(),
117 V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
118 _ => format!("{:?}", val),
119 })
120 }
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 pub fn get_serde_value<T: DeserializeOwned>(
126 &self,
127 key: &str,
128 ) -> Result<Option<T>, ConfigGetValueError> {
129 use figment::error::{Error as FError, Kind::MissingField};
130 match self.0.extract_inner(key) {
131 Ok(v) => Ok(Some(v)),
132 Err(FError {
133 kind: MissingField(..),
134 ..
135 }) => Ok(None),
136 Err(e) => Err(into_internal!("Unexpected error looking up config value")(e).into()),
137 }
138 }
139
140 /// Override our current tree with the settings in `config`.
141 ///
142 /// `config` must be implement [`Serialize`](serde::Serialize),
143 /// and must serialize to a map.
144 ///
145 /// This operation follows the same as are used when reading
146 /// multiple configuration files in sequence,
147 /// where option settings in later files replace earlier ones.
148 #[allow(clippy::unnecessary_wraps)]
149 pub fn merge_from<T>(&mut self, config: &T) -> Result<(), ConfigError>
150 where
151 T: serde::Serialize,
152 {
153 let provider = figment::providers::Serialized::from(config, figment::Profile::Default);
154 let mut orig = figment::Figment::new();
155 std::mem::swap(&mut orig, &mut self.0);
156 self.0 = orig.merge(provider);
157 // Figment::merge handles errors by making the type of the figment itself into an error...
158 // but we don't want our API to rely on that, so we let method returna Result.
159 Ok(())
160 }
161}
162
163/// Rules for reconfiguring a running Arti instance.
164#[derive(Debug, Clone, Copy, Eq, PartialEq)]
165#[non_exhaustive]
166pub 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
175impl 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 pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
179 match self {
180 Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
181 Err(ReconfigureError::CannotChange {
182 field: field.as_ref().to_owned(),
183 })
184 }
185 Reconfigure::WarnOnFailures => {
186 tracing::warn!("Cannot change {} on a running client.", field.as_ref());
187 Ok(())
188 }
189 }
190 }
191}
192
193/// Resolves an `Option<Option<T>>` (in a builder) into an `Option<T>`
194///
195/// * If the input is `None`, this indicates that the user did not specify a value,
196/// and we therefore use `def` to obtain the default value.
197///
198/// * If the input is `Some(None)`, or `Some(Some(Default::default()))`,
199/// the user has explicitly specified that this config item should be null/none/nothing,
200/// so we return `None`.
201///
202/// * Otherwise the user provided an actual value, and we return `Some` of it.
203///
204/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
205///
206/// For consistency with other APIs in Arti, when using this,
207/// do not pass `setter(strip_option)` to derive_builder.
208///
209/// # ⚠ Stability Warning ⚠
210///
211/// We may significantly change this so that it is an method in an extension trait.
212//
213// This is an annoying AOI right now because you have to write things like
214// #[builder(field(build = r#"tor_config::resolve_option(&self.dns_port, || None)"#))]
215// pub(crate) dns_port: Option<u16>,
216// which recapitulates the field name. That is very much a bug hazard (indeed, in an
217// early version of some of this code I perpetrated precisely that bug).
218// Fixing this involves a derive_builder feature.
219pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
220where
221 T: Clone + Default + PartialEq,
222 DF: FnOnce() -> Option<T>,
223{
224 resolve_option_general(
225 input.as_ref().map(|ov| ov.as_ref()),
226 |v| v == &T::default(),
227 def,
228 )
229}
230
231/// Resolves an `Option<Option<&T>>` (in a builder) into an `Option<T>`, more generally
232///
233/// Like [`resolve_option`], but:
234///
235/// * Doesn't rely on `T` being `Default + PartialEq`
236/// to determine whether it's the sentinel value;
237/// instead, takes `is_sentinel`.
238///
239/// * Takes `Option<Option<&T>>` which is more general, but less like the usual call sites.
240///
241/// # Behavior
242///
243/// * If the input is `None`, this indicates that the user did not specify a value,
244/// and we therefore use `def` to obtain the default value.
245///
246/// * If the input is `Some(None)`, or `Some(Some(v))` where `is_sentinel(v)` returns true,
247/// the user has explicitly specified that this config item should be null/none/nothing,
248/// so we return `None`.
249///
250/// * Otherwise the user provided an actual value, and we return `Some` of it.
251///
252/// See <https://gitlab.torproject.org/tpo/core/arti/-/issues/488>
253///
254/// # ⚠ Stability Warning ⚠
255///
256/// We may significantly change this so that it is an method in an extension trait.
257///
258/// # Example
259/// ```
260/// use tor_config::resolve_option_general;
261///
262/// // Use 0 as a sentinel meaning "explicitly clear" in this example
263/// let is_sentinel = |v: &i32| *v == 0;
264///
265/// // No value provided: use default
266/// assert_eq!(
267/// resolve_option_general(None, is_sentinel, || Some(10)),
268/// Some(10),
269/// );
270///
271/// // Explicitly None
272/// assert_eq!(
273/// resolve_option_general(Some(None), is_sentinel, || Some(10)),
274/// None,
275/// );
276///
277/// // Sentinel value (0) -> return None
278/// assert_eq!(
279/// resolve_option_general(Some(Some(&0)), is_sentinel, || Some(10)),
280/// None,
281/// );
282///
283/// // Set to actual value -> return that value
284/// assert_eq!(
285/// resolve_option_general(Some(Some(&5)), is_sentinel, || Some(10)),
286/// Some(5),
287/// );
288/// ```
289pub fn resolve_option_general<T, ISF, DF>(
290 input: Option<Option<&T>>,
291 is_sentinel: ISF,
292 def: DF,
293) -> Option<T>
294where
295 T: Clone,
296 DF: FnOnce() -> Option<T>,
297 ISF: FnOnce(&T) -> bool,
298{
299 match input {
300 None => def(),
301 Some(None) => None,
302 Some(Some(v)) if is_sentinel(v) => None,
303 Some(Some(v)) => Some(v.clone()),
304 }
305}
306
307/// Defines standard impls for a struct with a `Builder`, incl `Default`
308///
309/// **Use this.** Do not `#[derive(Builder, Default)]`. That latter approach would produce
310/// wrong answers if builder attributes are used to specify non-`Default` default values.
311///
312/// # Input syntax
313///
314/// ```
315/// use derive_builder::Builder;
316/// use serde::{Deserialize, Serialize};
317/// use tor_config::impl_standard_builder;
318/// use tor_config::ConfigBuildError;
319///
320/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
321/// #[builder(derive(Serialize, Deserialize, Debug))]
322/// #[builder(build_fn(error = "ConfigBuildError"))]
323/// struct SomeConfigStruct { }
324/// impl_standard_builder! { SomeConfigStruct }
325///
326/// #[derive(Debug, Builder, Clone, Eq, PartialEq)]
327/// struct UnusualStruct { }
328/// impl_standard_builder! { UnusualStruct: !Deserialize + !Builder }
329/// ```
330///
331/// # Requirements
332///
333/// `$Config`'s builder must have default values for all the fields,
334/// or this macro-generated self-test will fail.
335/// This should be OK for all principal elements of our configuration.
336///
337/// `$ConfigBuilder` must have an appropriate `Deserialize` impl.
338///
339/// # Options
340///
341/// * `!Default` suppresses the `Default` implementation, and the corresponding tests.
342/// This should be done within Arti's configuration only for sub-structures which
343/// contain mandatory fields (and are themselves optional).
344///
345/// * `!Deserialize` suppresses the test case involving `Builder: Deserialize`.
346/// This should not be done for structs which are part of Arti's configuration,
347/// but can be appropriate for other types that use [`derive_builder`].
348///
349/// * `!Builder` suppresses the impl of the [`tor_config::load::Builder`](load::Builder) trait
350/// This will be necessary if the error from the builder is not [`ConfigBuildError`].
351///
352/// # Generates
353///
354/// * `impl Default for $Config`
355/// * `impl Builder for $ConfigBuilder`
356/// * a self-test that the `Default` impl actually works
357/// * a test that the `Builder` can be deserialized from an empty [`ConfigurationTree`],
358/// and then built, and that the result is the same as the ordinary default.
359//
360// The implementation munches fake "trait bounds" (`: !Deserialize + !Wombat ...`) off the RHS.
361// We're going to add at least one more option.
362//
363// When run with `!Default`, this only generates a `builder` impl and an impl of
364// the `Resolvable` trait which probably won't be used anywhere. That may seem
365// like a poor tradeoff (much fiddly macro code to generate a trivial function in
366// a handful of call sites). However, this means that `impl_standard_builder!`
367// can be used in more places. That sets a good example: always use the macro.
368//
369// That is a good example because we want `impl_standard_builder!` to be
370// used elsewhere because it generates necessary tests of properties
371// which might otherwise be violated. When adding code, people add according to the
372// patterns they see.
373//
374// (We, sadly, don't have a good way to *ensure* use of `impl_standard_builder`.)
375#[macro_export]
376macro_rules! impl_standard_builder {
377 // Convert the input into the "being processed format":
378 {
379 $Config:ty $(: $($options:tt)* )?
380 } => { $crate::impl_standard_builder!{
381 // ^Being processed format:
382 @ ( Builder )
383 ( default )
384 ( extract ) $Config : $( $( $options )* )?
385 // ~~~~~~~~~~~~~~~ ^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
386 // present iff not !Builder, !Default
387 // present iff not !Default
388 // present iff not !Deserialize type always present options yet to be parsed
389 } };
390 // If !Deserialize is the next option, implement it by making $try_deserialize absent
391 {
392 @ ( $($Builder :ident)? )
393 ( $($default :ident)? )
394 ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )*
395 } => { $crate::impl_standard_builder!{
396 @ ( $($Builder )? )
397 ( $($default )? )
398 ( ) $Config : $( $options )*
399 } };
400 // If !Builder is the next option, implement it by making $Builder absent
401 {
402 @ ( $($Builder :ident)? )
403 ( $($default :ident)? )
404 ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Builder $( $options:tt )*
405 } => { $crate::impl_standard_builder!{
406 @ ( )
407 ( $($default )? )
408 ( $($try_deserialize )? ) $Config : $( $options )*
409 } };
410 // If !Default is the next option, implement it by making $default absent
411 {
412 @ ( $($Builder :ident)? )
413 ( $($default :ident)? )
414 ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Default $( $options:tt )*
415 } => { $crate::impl_standard_builder!{
416 @ ( $($Builder )? )
417 ( )
418 ( $($try_deserialize )? ) $Config : $( $options )*
419 } };
420 // Having parsed all options, produce output:
421 {
422 @ ( $($Builder :ident)? )
423 ( $($default :ident)? )
424 ( $($try_deserialize:ident)? ) $Config:ty : $(+)?
425 } => { $crate::deps::paste!{
426 impl $Config {
427 /// Returns a fresh, default, builder
428 pub fn builder() -> [< $Config Builder >] {
429 Default::default()
430 }
431 }
432
433 $( // expands iff there was $default, which is always default
434 impl Default for $Config {
435 fn $default() -> Self {
436 // unwrap is good because one of the test cases above checks that it works!
437 [< $Config Builder >]::default().build().unwrap()
438 }
439 }
440 )?
441
442 $( // expands iff there was $Builder, which is always Builder
443 impl $crate::load::$Builder for [< $Config Builder >] {
444 type Built = $Config;
445 fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
446 [< $Config Builder >]::build(self)
447 }
448 }
449 )?
450
451 #[test]
452 #[allow(non_snake_case)]
453 fn [< test_impl_Default_for_ $Config >] () {
454 #[allow(unused_variables)]
455 let def = None::<$Config>;
456 $( // expands iff there was $default, which is always default
457 let def = Some($Config::$default());
458 )?
459
460 if let Some(def) = def {
461 $( // expands iff there was $try_deserialize, which is always extract
462 let empty_config = $crate::deps::figment::Figment::new();
463 let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
464 let from_empty = builder.build().unwrap();
465 assert_eq!(def, from_empty);
466 )*
467 }
468 }
469 } };
470}
471
472#[cfg(test)]
473mod test {
474 // @@ begin test lint list maintained by maint/add_warning @@
475 #![allow(clippy::bool_assert_comparison)]
476 #![allow(clippy::clone_on_copy)]
477 #![allow(clippy::dbg_macro)]
478 #![allow(clippy::mixed_attributes_style)]
479 #![allow(clippy::print_stderr)]
480 #![allow(clippy::print_stdout)]
481 #![allow(clippy::single_char_pattern)]
482 #![allow(clippy::unwrap_used)]
483 #![allow(clippy::unchecked_time_subtraction)]
484 #![allow(clippy::useless_vec)]
485 #![allow(clippy::needless_pass_by_value)]
486 #![allow(clippy::string_slice)] // See arti#2571
487 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
488 use super::*;
489 use crate::{self as tor_config, sources::MustRead};
490 use derive_builder::Builder;
491 use serde::{Deserialize, Serialize};
492 use serde_json::json;
493 use tracing_test::traced_test;
494
495 #[test]
496 #[traced_test]
497 fn reconfigure_helpers() {
498 let how = Reconfigure::AllOrNothing;
499 let err = how.cannot_change("the_laws_of_physics").unwrap_err();
500 assert_eq!(
501 err.to_string(),
502 "Cannot change the_laws_of_physics on a running client.".to_owned()
503 );
504
505 let how = Reconfigure::WarnOnFailures;
506 let ok = how.cannot_change("stuff");
507 assert!(ok.is_ok());
508 assert!(logs_contain("Cannot change stuff on a running client."));
509 }
510
511 #[test]
512 #[rustfmt::skip] // autoformatting obscures the regular structure
513 fn resolve_option_test() {
514 #[derive(Debug, Clone, Builder, Eq, PartialEq)]
515 #[builder(build_fn(error = "ConfigBuildError"))]
516 #[builder(derive(Debug, Serialize, Deserialize, Eq, PartialEq))]
517 struct TestConfig {
518 #[builder(field(build = r#"tor_config::resolve_option(&self.none, || None)"#))]
519 none: Option<u32>,
520
521 #[builder(field(build = r#"tor_config::resolve_option(&self.four, || Some(4))"#))]
522 four: Option<u32>,
523 }
524
525 // defaults
526 {
527 let builder_from_json: TestConfigBuilder = serde_json::from_value(
528 json!{ { } }
529 ).unwrap();
530
531 let builder_from_methods = TestConfigBuilder::default();
532
533 assert_eq!(builder_from_methods, builder_from_json);
534 assert_eq!(builder_from_methods.build().unwrap(),
535 TestConfig { none: None, four: Some(4) });
536 }
537
538 // explicit positive values
539 {
540 let builder_from_json: TestConfigBuilder = serde_json::from_value(
541 json!{ { "none": 123, "four": 456 } }
542 ).unwrap();
543
544 let mut builder_from_methods = TestConfigBuilder::default();
545 builder_from_methods.none(Some(123));
546 builder_from_methods.four(Some(456));
547
548 assert_eq!(builder_from_methods, builder_from_json);
549 assert_eq!(builder_from_methods.build().unwrap(),
550 TestConfig { none: Some(123), four: Some(456) });
551 }
552
553 // explicit "null" values
554 {
555 let builder_from_json: TestConfigBuilder = serde_json::from_value(
556 json!{ { "none": 0, "four": 0 } }
557 ).unwrap();
558
559 let mut builder_from_methods = TestConfigBuilder::default();
560 builder_from_methods.none(Some(0));
561 builder_from_methods.four(Some(0));
562
563 assert_eq!(builder_from_methods, builder_from_json);
564 assert_eq!(builder_from_methods.build().unwrap(),
565 TestConfig { none: None, four: None });
566 }
567
568 // explicit None (API only, serde can't do this for Option)
569 {
570 let mut builder_from_methods = TestConfigBuilder::default();
571 builder_from_methods.none(None);
572 builder_from_methods.four(None);
573
574 assert_eq!(builder_from_methods.build().unwrap(),
575 TestConfig { none: None, four: None });
576 }
577 }
578
579 #[test]
580 fn get_value() {
581 use serde_value::Value as V;
582 let to_value = |json_str: &str| {
583 serde_value::to_value(serde_json::from_str::<serde_json::Value>(json_str).unwrap())
584 .unwrap()
585 };
586 let mut sources = ConfigurationSources::new_empty();
587
588 let source = "
589 [foo]
590 bar.baz = 7
591 quux = [[],[],{}]
592 ";
593 let source = ConfigurationSource::from_verbatim(source.to_string());
594 sources.push_source(source, MustRead::MustRead);
595
596 let tree = sources.load().unwrap();
597
598 {
599 let v1 = tree.get_serde_value::<V>("foo.quux").unwrap().unwrap();
600 let v2 = to_value(r#"[[], [], {}]"#);
601 assert_eq!(v1, v2);
602 }
603
604 assert!(tree.get_serde_value::<V>("nonexist").unwrap().is_none());
605 assert!(tree.get_serde_value::<V>("foo.nonexist").unwrap().is_none());
606 assert!(
607 tree.get_serde_value::<V>("foo.quux.nonexist")
608 .unwrap()
609 .is_none()
610 );
611 }
612}