diff --git a/crates/tor-config/src/lib.rs b/crates/tor-config/src/lib.rs index 9afc3c0c7..1e49fe137 100644 --- a/crates/tor-config/src/lib.rs +++ b/crates/tor-config/src/lib.rs @@ -53,6 +53,7 @@ mod mut_cfg; mod path; pub use cmdline::CmdLine; +pub use config as config_crate; pub use educe; pub use err::{ConfigBuildError, ReconfigureError}; pub use mut_cfg::MutCfg; @@ -94,33 +95,87 @@ impl Reconfigure { /// Defines standard impls for a struct with a `Builder`, incl `Default` /// -/// Generates: -/// -/// * `impl Default for $Config` -/// * a self-test that this actually works -/// /// **Use this.** Do not `#[derive(Builder, Default)]`. That latter approach would produce /// wrong answers if builder attributes are used to specify non-`Default` default values. /// +/// # Input syntax +/// +/// ``` +/// use derive_builder::Builder; +/// use serde::{Deserialize, Serialize}; +/// use tor_config::impl_standard_builder; +/// use tor_config::ConfigBuildError; +/// +/// #[derive(Debug, Builder, Clone, Eq, PartialEq)] +/// #[builder(derive(Serialize, Deserialize, Debug))] +/// #[builder(build_fn(error = "ConfigBuildError"))] +/// struct SomeConfigStruct { } +/// impl_standard_builder! { SomeConfigStruct } +/// +/// #[derive(Debug, Builder, Clone, Eq, PartialEq)] +/// struct UnusualStruct { } +/// impl_standard_builder! { UnusualStruct: !Deserialize } +/// ``` +/// +/// # Requirements +/// /// `$Config`'s builder must have default values for all the fields, /// or this macro-generated self-test will fail. -/// This should be OK for all elements of our configuration. +/// This should be OK for all principal elements of our configuration. +/// +/// `$ConfigBuilder` must have an appropriate `Deserialize` impl. +/// +/// # Options +/// +/// * `!Deserialize` suppresses the test case involving `Builder: Deserialize`. +/// This should not be done for structs which are part of Arti's configuration, +/// but can be appropriate for other types that use [`derive_builder`]. +/// +/// # Generates +/// +/// * `impl Default for $Config` +/// * a self-test that the `Default` impl actually works +/// * a test that the `Builder` can be deserialized from an empty [`config::Config`], +/// and then built, and that the result is the same as the ordinary default. +// +// The implementation munches fake "trait bounds" off the RHS. +// We're going to add at least one more option. #[macro_export] -macro_rules! impl_standard_builder { { - $Config:ty -} => { - $crate::paste!{ +macro_rules! impl_standard_builder { + { + $Config:ty $(: $($options:tt)* )? + } => { $crate::impl_standard_builder!{ + @ ( try_deserialize ) $Config : $( $( $options )* )? + } }; + { + @ ( $($try_deserialize:ident)? ) $Config:ty : $(+)? !Deserialize $( $options:tt )* + } => { $crate::impl_standard_builder!{ + @ ( ) $Config : $( $options )* + } }; + { + @ ( $($try_deserialize:ident)? ) $Config:ty : $(+)? + } => { $crate::paste!{ impl Default for $Config { fn default() -> Self { - // unwrap is good because the test case above checks that it works! + // unwrap is good because one of the test cases above checks that it works! [< $Config Builder >]::default().build().unwrap() } } #[test] - fn [< test_impl_Default_for_ $Config >] () { let _ = $Config::default(); } - } -} } + fn [< test_impl_Default_for_ $Config >] () { + #[allow(unused_variables)] + let def = $Config::default(); + + $( // expands iff there was $try_deserialize, which is always try_deserialize + let empty_config = $crate::config_crate::Config::builder().build().unwrap(); + let builder: [< $Config Builder >] = empty_config.$try_deserialize().unwrap(); + let from_empty = builder.build().unwrap(); + assert_eq!(def, from_empty); + )* + } + } }; +} #[cfg(test)] mod test { diff --git a/crates/tor-guardmgr/src/lib.rs b/crates/tor-guardmgr/src/lib.rs index f1183df3e..2de44162a 100644 --- a/crates/tor-guardmgr/src/lib.rs +++ b/crates/tor-guardmgr/src/lib.rs @@ -1234,7 +1234,7 @@ pub struct GuardUsage { restrictions: GuardRestrictionList, } -impl_standard_builder! { GuardUsage } +impl_standard_builder! { GuardUsage: !Deserialize } /// List of socket restricteionesses, as configured pub type GuardRestrictionList = Vec;