tor_config/lib.rs
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
47pub mod cmdline;
48mod err;
49#[macro_use]
50pub mod extend_builder;
51pub mod file_watcher;
52mod flatten;
53pub mod list_builder;
54mod listen;
55pub mod load;
56pub mod map_builder;
57mod misc;
58pub mod mistrust;
59mod mut_cfg;
60pub mod sources;
61#[cfg(feature = "testing")]
62pub mod testing;
63
64#[doc(hidden)]
65pub 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
75pub use cmdline::CmdLine;
76pub use err::{ConfigBuildError, ConfigError, ReconfigureError};
77pub use flatten::{Flatten, Flattenable};
78pub use list_builder::{MultilineListBuilder, MultilineListBuilderError};
79pub use listen::*;
80pub use load::{resolve, resolve_ignore_warnings, resolve_return_results};
81pub use misc::*;
82pub use mut_cfg::MutCfg;
83pub use sources::{ConfigurationSource, ConfigurationSources};
84
85use itertools::Itertools;
86
87#[doc(hidden)]
88pub use derive_deftly;
89#[doc(hidden)]
90pub use flatten::flattenable_extract_fields;
91
92derive_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)]
100pub struct ConfigurationTree(figment::Figment);
101
102#[cfg(test)]
103impl ConfigurationTree {
104 #[cfg(test)]
105 pub(crate) fn get_string(&self, key: &str) -> Result<String, crate::ConfigError> {
106 use figment::value::Value as V;
107 let val = self.0.find_value(key).map_err(ConfigError::from_cfg_err)?;
108 Ok(match val {
109 V::String(_, s) => s.to_string(),
110 V::Num(_, n) => n.to_i128().expect("Failed to extract i128").to_string(),
111 _ => format!("{:?}", val),
112 })
113 }
114}
115
116/// Rules for reconfiguring a running Arti instance.
117#[derive(Debug, Clone, Copy, Eq, PartialEq)]
118#[non_exhaustive]
119pub 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
128impl 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 pub fn cannot_change<S: AsRef<str>>(self, field: S) -> Result<(), ReconfigureError> {
132 match self {
133 Reconfigure::AllOrNothing | Reconfigure::CheckAllOrNothing => {
134 Err(ReconfigureError::CannotChange {
135 field: field.as_ref().to_owned(),
136 })
137 }
138 Reconfigure::WarnOnFailures => {
139 tracing::warn!("Cannot change {} on a running client.", field.as_ref());
140 Ok(())
141 }
142 }
143 }
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.
172pub fn resolve_option<T, DF>(input: &Option<Option<T>>, def: DF) -> Option<T>
173where
174 T: Clone + Default + PartialEq,
175 DF: FnOnce() -> Option<T>,
176{
177 resolve_option_general(
178 input.as_ref().map(|ov| ov.as_ref()),
179 |v| v == &T::default(),
180 def,
181 )
182}
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
213pub fn resolve_option_general<T, ISF, DF>(
214 input: Option<Option<&T>>,
215 is_sentinel: ISF,
216 def: DF,
217) -> Option<T>
218where
219 T: Clone,
220 DF: FnOnce() -> Option<T>,
221 ISF: FnOnce(&T) -> bool,
222{
223 match input {
224 None => def(),
225 Some(None) => None,
226 Some(Some(v)) if is_sentinel(v) => None,
227 Some(Some(v)) => Some(v.clone()),
228 }
229}
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.
287pub fn resolve_alternative_specs<V, K>(
288 specified: impl IntoIterator<Item = (K, Option<V>)>,
289 default: impl FnOnce() -> V,
290) -> Result<V, ConfigBuildError>
291where
292 K: Into<String>,
293 V: Eq,
294{
295 Ok(specified
296 .into_iter()
297 .filter_map(|(k, v)| Some((k, v?)))
298 .dedup_by(|(_, v1), (_, v2)| v1 == v2)
299 .at_most_one()
300 .map_err(|several| ConfigBuildError::Inconsistent {
301 fields: several.into_iter().map(|(k, _v)| k.into()).collect_vec(),
302 problem: "conflicting fields, specifying different values".into(),
303 })?
304 .map(|(_k, v)| v)
305 .unwrap_or_else(default))
306}
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]
377macro_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 pub fn builder() -> [< $Config Builder >] {
430 Default::default()
431 }
432 }
433
434 $( // expands iff there was $default, which is always default
435 impl Default for $Config {
436 fn $default() -> Self {
437 // unwrap is good because one of the test cases above checks that it works!
438 [< $Config Builder >]::default().build().unwrap()
439 }
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 fn build(&self) -> std::result::Result<$Config, $crate::ConfigBuildError> {
447 [< $Config Builder >]::build(self)
448 }
449 }
450 )?
451
452 #[test]
453 #[allow(non_snake_case)]
454 fn [< test_impl_Default_for_ $Config >] () {
455 #[allow(unused_variables)]
456 let def = None::<$Config>;
457 $( // expands iff there was $default, which is always default
458 let def = Some($Config::$default());
459 )?
460
461 if let Some(def) = def {
462 $( // expands iff there was $try_deserialize, which is always extract
463 let empty_config = $crate::deps::figment::Figment::new();
464 let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap();
465 let from_empty = builder.build().unwrap();
466 assert_eq!(def, from_empty);
467 )*
468 }
469 }
470 } };
471}
472
473#[cfg(test)]
474mod 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}