//! Derive-deftly macro for deriving configuration objects. //! //! The macro defined here makes a configuration object //! able to participate in the arti configuration system by giving //! it a Builder type implementing the appropriate serde traits. //! //! It is more ergonomic and less error-prone for our purposes than //! `derive_builder`. //! //! ## Basic usage: //! //! ``` //! use derive_deftly::Deftly; //! use tor_config::derive::prelude::*; //! //! #[derive(Deftly, Clone, Debug, PartialEq)] //! #[derive_deftly(TorConfig)] //! pub struct ExampleConfig { //! #[deftly(tor_config(default))] //! owner_uid: Option, //! //! #[deftly(tor_config(default = r#" "~".to_string() "#))] //! path: String, //! } //! ``` //! For topic-specific information, see one of the following: //! //! * [How to declare a configuration type](doc_howto) //! * [Code generated by the tor_config macro](doc_generated_code) //! * [Reference: Attributes supported by the tor_config macro](doc_ref_attrs) //! * [Differences from derive_builder](doc_differences) //! * [Types with magic handling](doc_magic_types) /// How to declare a configuration type /// /// ## Getting started /// /// Every configuration type should implement at least `Clone` and `Debug`; /// it should almost[^partialeq-notest] always implement `PartialEq` too. /// In addition to these, you should use `derive(Deftly)` and `derive_deftly(TorConfig)` /// to use this macro. /// /// You can start out by copying this template: /// /// ``` /// use derive_deftly::Deftly; /// use tor_config::derive::prelude::*; /// /// #[derive(Deftly, Clone, Debug, PartialEq)] /// #[derive_deftly(TorConfig)] /// pub struct XyzzyConfig { /// // ... /// } /// ``` /// /// ## Declaring fields /// /// At this point, you start declaring the fields that should appear in the configuration. /// You declare them as struct fields (as usual); /// but to avoid mistakes, you need to specify the default behavior for each field. /// You typically do this in one of the following ways: /// /// ``` /// # use derive_deftly::Deftly; /// # use tor_config::derive::prelude::*; /// # fn some_function() -> u32 { 7 } /// # #[derive(Deftly, Clone, Debug, PartialEq)] /// # #[derive_deftly(TorConfig)] /// # pub struct Foo { /// /// // If the user doesn't set this field, we set it by calling some_function(). /// #[deftly(tor_config(default="some_function()"))] /// value1: u32, /// /// // If the user doesn't set this field, we set it by calling Default::default(). /// #[deftly(tor_config(default))] /// value2: String, /// /// // This field has no default! It's an error if the user doesn't set this. /// // (See warnings on no_default below.) /// #[deftly(tor_config(no_default))] /// value3: u16, /// # } /// ``` /// /// Notes: /// - There is no "default" behavior for the unset fields: you need to say which one you want. /// This requirement is meant to avoid programming errors. /// - Try to avoid using `no_default` on any configuration element unless you truly want the user /// to specify it in their configuration file every time this section exists. /// It's okay to use `no_default` /// on something like the address of a fallback directory /// (where each one needs to be fully specified) /// or the nickname of an onion service /// (which needs to be specified for every onion service that exists). /// - If you use `no_default`, you will need to use [`no_default_trait`] on the structure /// as a whole. /// /// ## Other things you can do /// /// There are many other attributes you can set on struct and fields /// to control their behavior. /// See [the reference](doc_ref_attrs) for more information. /// /// /// /// [^partialeq-notest]: If you decide not to implement `PartialEq` for some reason, /// you will need to use the [`no_test_default`] attribute to suppress a test that requires it. /// (I do not really know why you would do this. Just implement `PartialEq`, or /// edit this documentation to explain why people would sometimes not want to implement it.) /// /// [`no_default_trait`]: crate::derive::doc_ref_attrs#tmeta:no_default_trait /// [`no_test_default`]: crate::derive::doc_ref_attrs#tmeta:no_test_default pub mod doc_howto {} /// Code generated by the tor_config macro /// /// > Here we describe the code generated by the `derive_deftly(TorConfig)` macro. /// > Note that this is the _default_ behavior; /// > particular [attributes](doc_ref_attrs) not described here will override it. /// /// /// /// The `tor_config` macro generates a Builder struct, with a name formed by appending /// `Builder` to the name of the configuration struct. /// That is, if the configuration struct is `MyConfig`, /// the macro generates `MyConfigBuilder`. /// /// The builder struct has the same visibility as the configuration struct. /// The builder struct has, /// for every ordinary field[^ordinary] of type T in the configuration struct, /// a field with the same name and type `Option`. /// For every [sub-builder] field in the configuration struct of type `T`, /// it has a field with the same name and the type `TBuilder`. /// These fields are private. /// /// The builder struct implements [`serde::Serialize`] and [`serde::Deserialize`]. /// Every field is marked with `#[serde(default)]` to allow it to be absent /// in the toml configuration. /// /// The builder struct implements [`derive_deftly::Deftly`]. /// /// The builder struct derives `Default`, `Clone`, and `Debug`. /// It also has a `new()` method that calls `Default::default()`. /// /// The builder struct implements /// [`ExtendBuilder`](crate::extend_builder::ExtendBuilder) /// and [`Flattenable`](crate::Flattenable). /// /// The _configuration struct_ receives a generated "builder" method, /// of type `fn () -> MyConfigBuilder`. /// This function calls the builder's `Default::default()` method. /// /// For every ordinary[^ordinary] field of type T in the configuration struct, /// the builder has a **setter method** with the same name, /// of type `fn (&mut self, value: T) -> &mut Self`. /// The setter method sets the corresponding field in `self` to `Some(value)`, /// and returns `self``. /// /// For every [sub-builder] field of type SubCfg in the configuration struct, /// the builder has an accessor method with the same name, /// returning `&mut SubCfgBuilder`. /// /// The builder has a **build method**, with the name `build`, /// of type `fn (&self) -> Result`. /// It has the same visibility as the builder struct. /// For every field in the builder of value `Some(x)`, /// it sets the corresponding field in the configuration object to `x`. /// For every field in the builder of value `None`, /// it sets the corresponding field in the configuration object to its default, /// or returns an error, /// depending on the attributes set on the field. /// For every field in the builder with a [sub-builder], /// it invokes the build method on that sub-builder, /// and sets the corresponding field in the configuration object to its result. /// /// A new `#[cfg(test)]` module is generated, with tests for the builder behavior. /// This module is called `test_my_config_builder`, with `my_config` replaced with the /// snake-case name of your actual configuration type. /// /// [^ordinary]: For the purpose of this documentation, /// a field is "ordinary" if it does not have a [sub-builder]. /// /// /// [sub-builder]: crate::derive::doc_ref_attrs#fmeta:sub_builder pub mod doc_generated_code {} /// Reference: Attributes supported by the tor_config macro /// /// * [Top-level attributes](crate::derive::doc_ref_attrs#tmeta) /// * [Field-level attributes](crate::derive::doc_ref_attrs#fmeta) /// ///
/// /// ## Top-level attributes /// ///
/// /// These attributes can be provided at the top-level, right after /// `derive_deftly(TorConfig)` and before `pub struct FooConfig`. /// ///
/// /// ### `deftly(tor_config(no_serialize_trait))` — Don't derive Serialize for the builder struct /// ///
/// /// By default, the generated Builder will derive [`serde::Serialize`]. /// This attribute suppresses that behavior. /// /// ///
/// /// ### `deftly(tor_config(no_deserialize_trait))` — Don't derive Deserialize for the builder struct /// ///
/// /// By default, the generated Builder will implement [`serde::Deserialize`]. /// This attribute suppresses that behavior. /// /// Using this option will prevent your type from participating directly /// in the Arti configuration system. /// /// ///
/// /// ### `deftly(tor_config(no_flattenable_trait))` — Don't derive Flattenable for the builder struct /// ///
/// /// By default, the generated Builder will derive [`Flattenable`](crate::Flattenable). /// This attribute suppresses that behavior. /// ///
/// /// ### `deftly(tor_config(no_extendbuilder_trait))` — Don't derive ExtendBuilder for the builder struct /// ///
/// /// By default, the generated Builder will derive [`ExtendBuilder`]. /// This attribute suppresses that behavior. /// ///
/// /// ### `deftly(tor_config(no_default_trait))` — Don't derive Default for the config struct /// ///
/// /// By default, the macro will derive [`Default`] on the configuration type /// by creating a default builder, and constructing the configuration type with it. /// ///
/// /// ### `deftly(tor_config(no_test_default))` — Don't test Default for the config struct /// ///
/// /// By default, the macro will implement a test to make sure that its generated `Default` /// implementation produces the same result as deserializing an empty configuration builder, /// and building it. /// This attribute prevents that test from being generated. /// /// The test is also omitted when [`no_default_trait`] or [`no_deserialize_trait`] is given. /// /// > Note: You must specify this option if the configuration type has any generic parameters. /// > Otherwise, it's best to avoid this attribute. /// > /// > TODO: We should remove this limitation if we can. /// ///
/// /// ### `deftly(tor_config(no_builder_trait))` — Don't derive Builder for the builder struct /// ///
/// /// By default, the builder will implement [`tor_config::load::Builder`](crate::load::Builder). /// This attribute suppresses that behavior. /// /// > This attribute's name ends with `_trait` to remind the caller that the Builder struct itself /// > will still be implemented. /// ///
/// /// ### `deftly(tor_config(no_buildable_trait))` — Don't derive Buildable for the config struct /// ///
/// /// By default, the configuration struct will implement /// [`tor_config::load::Buildable`](crate::load::Buildable). /// This attribute suppresses that behavior. /// ///
/// /// ### `deftly(tor_config(attr = ".."))` — Apply an attribute to the builder struct /// ///
/// /// This attribute passes its contents through to a new attribute on the derived builder struct. /// For example, you can make the builder derive `PartialOrd` by saying /// /// ```no_compile /// #[deftly(tor_config(attr= "derive(PartialOrd)"))] /// ``` /// /// (See also [`attr`](crate::derive::doc_ref_attrs#fmeta:attr) for fields.) /// ///
/// /// ### `deftly(tor_config(pre_build = ".."))` — Call a function before building /// ///
/// /// This attribute makes the generated `build()` method call a validation function on itself /// **before** it builds the configuration. The function must take `&FooBuilder` /// as an argument, and return `Result<(),ConfigBuildError>`. If the function /// returns an error, then the build method fails with that error. /// /// Example: /// ``` /// # use derive_deftly::Deftly; /// # use tor_config::{derive::prelude::*, ConfigBuildError}; /// #[derive(Clone,Debug,PartialEq,Deftly)] /// #[derive_deftly(TorConfig)] /// #[deftly(tor_config(pre_build="Self::must_be_odd"))] /// pub struct FavoriteOddNumber { /// #[deftly(tor_config(default="23"))] /// my_favorite: u32, /// } /// /// impl FavoriteOddNumberBuilder { /// fn must_be_odd(&self) -> Result<(), ConfigBuildError> { /// let Some(fav) = self.my_favorite else { return Ok(()); }; /// if fav % 2 != 1 { /// return Err(ConfigBuildError::Invalid { /// field: "my_favorite".to_string(), /// problem: format!("{fav} was not an odd number") /// }) /// } /// Ok(()) /// } /// } /// ``` /// /// See also [`post_build`]. /// ///
/// /// ### `deftly(tor_config(post_build = ".."))` — Call a function after building /// ///
/// /// This attribute makes the generated `build()` method call a validation function /// on the configuration **after** it is built. /// The function must take the the configuration /// _by value_ as an argument, /// and `Result<`(the configuration)`,ConfigBuildError>`. If the function /// returns an error, then the build method fails with that error. /// /// Example: /// ``` /// # use derive_deftly::Deftly; /// # use tor_config::{derive::prelude::*, ConfigBuildError}; /// #[derive(Clone,Debug,PartialEq,Deftly)] /// #[derive_deftly(TorConfig)] /// #[deftly(tor_config(post_build="FavoriteEvenNumber::must_be_even"))] /// pub struct FavoriteEvenNumber { /// #[deftly(tor_config(default="86"))] /// my_favorite: u32, /// } /// /// impl FavoriteEvenNumber { /// fn must_be_even(self) -> Result { /// if self.my_favorite % 2 != 0 { /// return Err(ConfigBuildError::Invalid { /// field: "my_favorite".to_string(), /// problem: format!("{} was not an even number", self.my_favorite) /// }) /// } /// Ok(self) /// } /// } /// ``` /// /// > Note: /// > You can also use this attribute to clean up or normalize the the configuration object. /// ///
/// /// ### `deftly(tor_config(vis = ".."))` — Change visibility of the builder /// ///
/// /// By default, the builder struct is generated with the same visibility as the /// configuration struct. /// You can use this attribute to change its visibility. /// /// ///
/// /// ### `deftly(tor_config(build_fn(name = "..")))` — Change name of the build method /// ///
/// /// By default, the generated build method is called `build`. /// You can use this attribute to change its name. /// ///
/// /// ### `deftly(tor_config(build_fn(vis = "..")))` — Change visibility of the build method /// ///
/// /// By default, the `build()` method has the same visibility as the builder struct. /// You can use this attribute to change its visibility. /// /// See also: /// * [`deftly(tor_config(vis = ".."))`](crate::derive::doc_ref_attrs#tmeta:vis) /// ///
/// /// ### `deftly(tor_config(build_fn(error = "..")))` — Change return error type of the build method. /// ///
/// /// By default, the `build()` method returns [`ConfigBuildError` /// on failure. You can change the error type with this attribute. /// /// You will probably need to use this attribute along with [`build_fn(missing_field)`], /// if any of your fields are mandatory. /// ///
/// /// ### `deftly(tor_config(build_fn(missing_field = "..")))` — Code to generate a missing field error. /// ///
/// /// By default, when a required field is not set, the `build()` method returns /// `Err(E::MissingField { field: "field_name".to_string() })`, /// where `E` is [`ConfigBuildError`] or the type configured with [`build_fn(error)`]. /// You can use this attribute to override this behavior. /// Its value should be an expression evaluating to a closure of type /// `FnOnce(&str) -> E`. /// /// > For compatibility with `derive_builder`'s version of `build_fn(error)``, you can say: /// > ```no_compile /// > #[build_fn(error="SomeError", /// > missing_field= /// > r#"|fname| derive_builder::UninitializedFieldError( /// > fname.to_string() /// > ).into()" /// > )] /// > ``` /// ///
/// /// ## Field-level attributes /// ///
/// ///
/// /// ### `deftly(tor_config(default)))` — Use Default::default() when no value is provided /// ///
/// /// When this attribute is provided, if the given field is absent in the builder, /// the `build()` method will set its value to `Default::default()`. /// /// For each field, you must specify exactly one of /// [`default`], [`default =`], [`no_default`], [`build`], [`try_build`], or [`sub_builder`]. /// ///
/// /// ### `deftly(tor_config(default = "..")))` — Use a given value when no value is provided /// ///
/// /// When this attribute is provided, if the given field is absent in the builder, /// the `build()` method will set its value to the value of the provided expression. /// The expression may invoke a function, but cannot use `self`. /// /// For each field, you must specify exactly one of /// [`default`], [`default =`], [`no_default`], [`build`], [`try_build`], or [`sub_builder`]. /// /// ///
/// /// ### `deftly(tor_config(no_default))` — Do not provide a default for this field. /// ///
/// /// When this attribute is provided, if the given field is absent in the builder, /// the `build()` method will fail with an error. /// /// For each field, you must specify exactly one of /// [`default`], [`default =`], [`no_default`], [`build`], [`try_build`], or [`sub_builder`]. /// /// > When you set this attribute on a field, you must also set the top-level /// > [`no_default_trait`] attribute, /// > since there will not be a meaningful value for the configuration struct. /// > /// > Don't use this option on any config struct that's always present with a multiplicity of one, /// > or else the empty configuration will become invalid. /// ///
/// /// ### `deftly(tor_config(build = ".."))` — Call a function to build this field. /// ///
/// /// When this attribute is provided, you can completely override the way that this field is constructed. /// The expression must evaluate to a function taking `&Builder` as an argument, /// and returning the type of the field. /// /// For each field, you must specify exactly one of /// [`default`], [`default =`], [`no_default`], [`build`], [`try_build`], or [`sub_builder`]. /// /// > Be careful with this attribute: it can lead to counterintuitive behavior for the end user. /// /// Example: /// /// ``` /// # use derive_deftly::Deftly; /// # use tor_config::{derive::prelude::*, ConfigBuildError}; /// #[derive(Clone,Debug,PartialEq,Deftly)] /// #[derive_deftly(TorConfig)] /// pub struct LowercaseExample { /// #[deftly(tor_config(build="Self::build_lc"))] /// lc: String, /// } /// impl LowercaseExampleBuilder { /// fn build_lc(&self) -> String { /// let s = self.lc.as_ref().map(String::as_str).unwrap_or(""); /// s.to_lowercase() /// } /// } /// ``` /// ///
/// /// ### `deftly(tor_config(try_build = ".."))` — Call a fallible function to build this field. /// ///
/// /// When this attribute is provided, you can completely override the way that this field is constructed. /// The expression must evaluate to a function taking `&Builder` as an argument, /// and returning `Result`, where `T` is the type of the field. /// /// For each field, you must specify exactly one of /// [`default`], [`default =`], [`no_default`], [`build`], [`try_build`], or [`sub_builder`]. /// /// > Be careful with this attribute: it can lead to counterintuitive behavior for the end user. /// /// Example: /// /// ``` /// # use derive_deftly::Deftly; /// # use tor_config::{derive::prelude::*, ConfigBuildError}; /// #[derive(Clone,Debug,PartialEq,Deftly)] /// #[derive_deftly(TorConfig)] /// pub struct SqrtExample { /// #[deftly(tor_config(try_build="Self::build_sqrt"))] /// val: f64, /// } /// impl SqrtExampleBuilder { /// fn build_sqrt(&self) -> Result { /// let v = self.val.unwrap_or(0.0).sqrt(); /// if v.is_nan() { /// return Err(ConfigBuildError::Invalid { /// field: "val".to_string(), /// problem: format!("{v} was negative") /// }) /// } /// Ok(v) /// } /// } /// ``` /// ///
/// /// ### `deftly(tor_config(sub_builder))` — Declare a field to contain a nested configuration /// ///
/// /// This attribute is what allows configuration structures to nest. /// When you set this attribute on a field of type `InnerCfg`, /// the builder structure will contain a field of type `InnerCfgBuilder`. /// In order to construct the field, the `build()` method will call /// `InnerCfgBuilder::build()`. /// /// For each field, you must specify exactly one of /// [`default`], [`default =`], [`no_default`], [`build`], [`try_build`], or [`sub_builder`]. /// ///
/// /// ### `deftly(tor_config(sub_builder(build_fn = "...")))` — Call a different function on this sub-builder /// ///
/// /// By default, when [`sub_builder`] is in use, /// the `build()` method is used to generate the inner configuration object. /// This attribute changes the name of the function that is called on the inner builder. /// ///
/// /// ### `deftly(tor_config(no_sub_builder))` — Allow a Buildable field _without_ a sub_builder. /// ///
/// /// By default, the `TorConfig` macro inserts code to checks whether each field /// implements [`Buildable`], and causes a compile-time error if any such field /// does not have an explicit [`sub_builder`], [`build`], [`try_build`], /// or [`no_magic`] declaration. /// This attribute overrides this check, and allows you to have a field /// implementing [`Buildable`] without using the `sub_builder` pattern. /// /// ///
/// /// ### `deftly(tor_config(list))` — Declare a field to contain a nested list of items. /// ///
/// /// This attribute should be used on every field containing a `Vec`, /// `BTreeSet`, `HashSet`, or similar. /// It causes the an appropriate [list-builder](crate::list_builder) /// and set of accessors to be generated. /// /// When using this attribute, you must also provide a [`default =`] producing a `Vec` /// of the builder type, and either [`list(element(build))`] or [`list(element(clone))`]. /// /// Examples: /// /// ```no_compile /// // The builder and the constructed list will both contain u32. /// #[deftly(tor_config(list(element(clone)), default = "vec![7]"))] /// integers: Vec, /// /// // The builder will contain a Vec;. /// #[deftly(tor_config(list(), (element(build)), default = "vec![]"))] /// objects: Vec /// ``` /// ///
/// /// ### `deftly(tor_config(list(listtype = ...)))` /// ///
/// /// Usually, the name of the list type alias and builder object based on the struct and the field name. /// You can provide a different name for the list type alias using this attribute, as in /// `listtype = "TypeName"`. /// The list builder will then be constructed with the list type name, suffixed with `Builder`. /// /// /// ///
/// /// ### `deftly(tor_config(list(element(build)))` — Declare that the list builder contains sub-builders. /// ///
/// /// Used along with [`list`], /// and indicates the elements of the built list should be constructed via /// builders themselves. When this attribute is used, the builder structure /// will contain a Vec of builders for the list's elements. /// /// If this attribute is given a value, it will be used as the name of the /// "build" method for the list elements. /// ///
/// /// ### `deftly(tor_config(list(element(clone)))` — Declare that the list builder objects should be cloned directly. /// ///
/// /// Used along with [`list`], /// and indicates the elements of the built list should be cloned directly /// from those in the builder. When this attribute is used, the builder structure /// will contain a Vec whose elements are the same type as those of the genated list. /// ///
/// /// ### `deftly(tor_config(map))` — Use a map-builder pattern. /// ///
/// /// This attribute should be used on every field containing a `HashMap` or `BTreeMap` /// whose key is a `String`, and whose value type is a [`Buildable`]. /// It causes the template to generate a map builder type and corresponding accessor functions. /// The map builder behaves like a map from String to the builder type. /// /// If a value is provided for this attribute, it is used as the name of the /// map type alias, and suffixed with `Builder` to get the name for the builder type. /// Otherwise, the map type alias is derived from the name of the struct and the field. /// /// The [`default =`] attribute is mandatory with this attribute. /// /// For more information on the generated code, see /// [`define_map_builder`](crate::define_map_builder). /// ///
/// /// ### `deftly(tor_config(list(maptype = ...)))` /// ///
/// /// Usually, the name of the map type alias and builder object based on the struct and the field name. /// You can provide a different name for the map type alias using this attribute, as in /// `maptype = "TypeName"`. /// The map builder will then be constructed with the map type name, suffixed with `Builder`. /// ///
/// /// ### `deftly(tor_config(setter(name = "..")))` — Change the name of the setter function /// ///
/// /// By default, the setter function for a field has the same name as its field. /// You can provide a different name in this attribute. /// ///
/// /// ### `deftly(tor_config(setter(vis = "..")))` — Change the visibility of the setter function /// ///
/// /// By default, the setter function for a field has the same visibility as the builder type. /// You can provide a different visibility in this attribute. /// ///
/// /// ### `deftly(tor_config(setter(skip)))` — Do not generate a setter function /// ///
/// /// By default, the builder generates a setter function for every field. /// You can tell it not to do so for a single field by providing this attribute. /// ///
/// /// ### `deftly(tor_config(setter(into)))` — Have the setter function accept `impl Into` /// ///
/// /// By default, the setter function expects an argument of type `T`, /// where `T` is the same type of the field. /// When this option is provided, the setter instead expects an argument of type `impl Into`, /// and calls [`Into::into`] on it to set the field. /// ///
/// /// ### `deftly(tor_config(setter(try_into)))` — Have the setter function accept `impl TryInto` /// ///
/// /// By default, the setter function expects an argument of type `T`, /// where `T` is the same type of the field. /// When this option is provided, the setter instead expects an argument of type `impl TryInto`, /// and calls [`TryInto::try_into`] on it to set the field. /// If `try_into` returns an error, the setter function returns that error. /// ///
/// /// ### `deftly(tor_config(setter(strip_option)))` — Have the setter for `Option` accept `T` /// ///
/// /// This attribute requires that the field itself have type `Option` for some `T`. /// Instead of taking `Option` as an argument, the setter now accepts `T`. /// /// /// ///
/// /// ### `deftly(tor_config(field(ty = "..")))` — Change the type of a field in the builder /// ///
/// /// By default, for every field of type `T` in the configuration, /// the builder has a field of type `Option`. /// This attribute changes the type of the field in the builder to the provided type. /// /// > This attribute has no effect on the generated setter or builder code. /// > Therefore, you will typically need to use it along with /// > the [`setter(skip)`], [`build`], and [`extend_with`] field attributes. /// /// Example: /// /// ``` /// # #![allow(unexpected_cfgs)] /// # use derive_deftly::Deftly; /// # use tor_config::{derive::prelude::*, ConfigBuildError}; /// #[derive(Clone,Debug,PartialEq)] /// pub struct ParsedValue { /// // ... /// } /// impl std::str::FromStr for ParsedValue { /// type Err = String; /// fn from_str(s: &str) -> Result { /// // ... /// # unimplemented!() /// } /// } /// /// #[derive(Clone,Debug,PartialEq,Deftly)] /// #[derive_deftly(TorConfig)] /// pub struct MyConfig { /// #[deftly(tor_config( /// try_build = r#"Self::try_build_behavior"#, /// field(ty = "Option"), /// setter(skip) /// ))] /// behavior: ParsedValue, /// } /// /// impl MyConfigBuilder { /// pub fn behavior(&mut self, s: impl AsRef) -> &mut Self { /// self.behavior = Some(s.as_ref().to_string()); /// self /// } /// fn try_build_behavior(&self) -> Result { /// self.behavior /// .as_ref() /// .map(String::as_str) /// .unwrap_or("Leave the macro processor. Take the cannoli.") /// .parse() /// .map_err(|problem| ConfigBuildError::Invalid { /// field: "behavior".to_string(), /// problem, /// }) /// } /// } /// ``` /// ///
/// /// ### `deftly(tor_config(field(vis = "..")))` — Change the visibility of a field in the builder /// ///
/// /// By default, fields in the builder are private. /// This attribute changes their visibility to the one provided. /// /// ///
/// /// ### `deftly(tor_config(skip))` — Do not generate the field in the builder /// ///
/// /// If this attribute is present, no field is generated in the builder for this field. /// Implies [`setter(skip)`]. Requires [`build`]. /// ///
/// /// ### `deftly(tor_config(attr = "..")))` — Apply an attribute to the field in the builder /// ///
/// /// Any attribute provided here is applied to the declared field in the builder. /// /// Example: /// ```no_compile /// #[deftly(tor_config(attr = "allow(deprecated)"))] /// x: SomeDeprecatedType, /// ``` /// /// See also the [`cfg`](crate::derive::doc_ref_attrs#fmeta:cfg) and [`serde`] attributes. /// ///
/// /// ### `deftly(tor_config(serde = "..")))` — Apply a serde attribute to the field in the builder /// ///
/// /// Any serde attribute provided here is applied to the declared field in the builder. /// /// Example: /// ```no_compile /// #[deftly(tor_config(serde = r#"alias = "old_name_of_field" "#))] /// current_name_of_field: String, /// ``` /// /// Attributes applied with `serde` apply after any specified with /// [`attr`](crate::derive::doc_ref_attrs#fmeta:attr) instead. /// /// > This is a convenience attribute; you could just use /// > [`attr`](crate::derive::doc_ref_attrs#fmeta:attr) instead. /// /// /// ///
/// /// ### `deftly(tor_config(extend_with = ""))` — Change the ExtendBuilder behavior for a field. /// ///
/// /// Unless you specify [`no_extendbuilder_trait`], the builder type will /// implement [`ExtendBuilder`], /// and will need a way to replace or extend every field in the builder with /// the value from another builder. /// This attribute lets you override the default behavior for a single field. /// It expects an expression that evaluates to type /// `FnOnce(&mut T, T, `[`ExtendStrategy`]`)`, /// where `T` is the type of the field in the builder. /// ///
/// /// ### `deftly(tor_config(cfg = "..")))` — Mark a field as conditionally present /// ///
/// /// This option causes a field in the builder to be conditionally present or absent at compile time, /// similar to the ordinary [`cfg`] attribute. /// However, unlike with the ordinary `cfg` attribute, /// if the user provides any configuration values for this field when it is disabled, /// the generated `build()` code will emit a warning via [`tracing`] at runtime telling them /// what feature they would need to turn on. /// /// > Note that you _cannot_ use this along with a regular [`cfg`] attribute, /// > since a regular [`cfg`] attribute would suppress the field altogether. /// > Therefore, the field will still be present in the configuration struct /// > even when the `cfg` condition is false. /// > To work around this limitation, it's conventional to arrange for the type of the field /// > to be `()` when the `cfg` condition is false. /// /// The `tor_config(cfg_desc)` attribute is mandatory to use along with `cfg`. /// It should contain a short prepositional phrase /// describing how the program needs to be built with /// in order to make this feature present. /// Examples might be "with RPC support" or "for Windows". /// /// /// Example: /// ``` /// # #![allow(unexpected_cfgs)] /// # use derive_deftly::Deftly; /// # use tor_config::{derive::prelude::*, ConfigBuildError}; /// #[cfg(feature = "rpc")] /// pub type RpcOptionType = String; /// /// #[cfg(not(feature = "rpc"))] /// type RpcOptionType = (); /// /// #[derive(Clone,Debug,PartialEq,Deftly)] /// #[derive_deftly(TorConfig)] /// pub struct OnionSoupConfig { /// #[deftly(tor_config(cfg = r#" feature="rpc" "#, cfg_desc = "with RPC support"))] /// #[deftly(tor_config(default))] /// rpc_option: RpcOptionType, /// } /// ``` /// /// /// ///
/// /// ### `deftly(tor_config(cfg_desc = "..")))` — Description of when a field is present. /// ///
/// /// Used along with [`tor_config(cfg)`](#fmeta:cfg); see that attribute's documentation. /// ///
/// /// ### `deftly(tor_config(no_magic)))` — Disable magic handling based on a field's type /// ///
/// /// This attribute disables [type-based magic behavior](crate::derive::doc_magic_types) /// for the current field. /// /// [`build_fn(error)`]: crate::derive::doc_ref_attrs#tmeta:build_fn_error /// [`build_fn(missing_field)`]: crate::derive::doc_ref_attrs#tmeta:build_fn_missing_field /// [`build`]: crate::derive::doc_ref_attrs#fmeta:build /// [`Buildable`]: crate::load::Buildable /// [`ConfigBuildError`]: crate::ConfigBuildError /// [`default =`]: crate::derive::doc_ref_attrs#fmeta:default_equals /// [`default`]: crate::derive::doc_ref_attrs#fmeta:default_default /// [`extend_with`]: crate::derive::doc_ref_attrs#fmeta:extend_with /// [`ExtendBuilder`]: crate::extend_builder::ExtendBuilder /// [`ExtendStrategy`]: crate::extend_builder::ExtendStrategy /// [`list`]: crate::derive::doc_ref_attrs#fmeta:list /// [`list(listtype)`]: crate::derive::doc_ref_attrs#fmeta:list_listtype /// [`list(element(build))`]: crate::derive::doc_ref_attrs#fmeta:list_element_build /// [`list(element(clone))`]: crate::derive::doc_ref_attrs#fmeta:list_element_clone /// [`no_default`]: crate::derive::doc_ref_attrs#fmeta:no_default /// [`no_default_trait`]: crate::derive::doc_ref_attrs#tmeta:no_default_trait /// [`no_deserialize_trait`]: crate::derive::doc_ref_attrs#tmeta:no_deserialize_trait /// [`no_extendbuilder_trait`]: crate::derive::doc_ref_attrs#tmeta:no_extendbuilder_trait /// [`no_flattenable_trait`]: crate::derive::doc_ref_attrs#tmeta:no_flattenable_trait /// [`no_magic`]: crate::derive::doc_ref_attrs#fmeta:no_default /// [`post_build`]: crate::derive::doc_ref_attrs#tmeta:post_build /// [`serde`]: crate::derive::doc_ref_attrs#fmeta:serde /// [`setter(skip)`]: crate::derive::doc_ref_attrs#fmeta:setter_skip /// [`sub_builder`]: crate::derive::doc_ref_attrs#fmeta:sub_builder /// [`try_build`]: crate::derive::doc_ref_attrs#fmeta:try_build pub mod doc_ref_attrs {} /// Differences from `derive_builder` /// /// * Not all derive_builder attributes have been cloned: only the ones that we used. /// * Attributes have been adjusted where possible to be easier to use. /// * It is not necessary to use `impl_standard_builder!` /// * The appropriate `serde` attributes are automatically provided on the builder. /// * Every field must have some default behavior specified, or must have the `no_default` option given. /// (With `derive_builder`, `no_default` is the default, which can result in confusing test failures.) /// * Sub-builders are supported. /// * The `cfg` attribute can be used to warn when the user /// tries to provide a value for an compiled-out field. /// * There is (limited) support for generics. /// * The generated documentation is a little better. /// * `validate` has been renamed to `pre_build`. /// * There is `post_build` attribute to replace the pattern where we would rename the build function, /// make it private, and wrap it. /// * A top-level `build_fn` does not override the build function on sub-builders. /// * The list builder and map builder patterns are supported via attributes; you don't need separate /// macros for them. /// * Builders automatically derive `ExtendBuilder`, `Flattenable`, and `Builder`. /// * The configuration type automatically derives `Buildable`. /// * Visibility is set more reasonably. /// * For many types, we automatically generate setters or serde code that conforms /// to our standards. pub mod doc_differences {} /// # Types with "magic" handling /// /// The `derive_deftly(TorConfig)` macro currently has automatic "magic" /// handling for a few types, and a few other types where it can "magically" /// detect that you forgot to use a certain option that you probably wanted. /// /// To override the magic handling for a field, use the [`tor_config(no_magic)`] /// attribute on that field. /// /// These types are handled on a best-effort basis by matching their names. /// If you define a field to have another type that is an alias one of these, /// the macro won't be able to handle it. /// To prevent this case, we use [`assert_not_impl`] /// to insure that you will get a compile-time error if you use one of these /// types under a different name without specifying [`tor_config(no_magic)`]. /// /// The types with magic handling are: /// /// ## `String` /// /// The setter for each [`String`] field takes `impl `[`StringOrStr`] as its argument. /// This trait is implemented by `String` and `&str`. /// /// > Earlier we had considered having it take `AsRef`, but this is too general /// > and can lead to unwanted surprises. /// /// ## `NonZero` /// /// The setter for a [`NonZero`]`` field takes /// `impl `[`PossiblyBoundsChecked`]`` as its argument. /// This trait is implemented by `T` and by [`NonZero`]``. /// /// When the build() function is called, it returns an error if the provided /// value was zero. /// /// ## `Duration` /// /// The serde implementations for a [`Duration`](std::time::Duration) field /// use [`humantime_serde`] to accept and generate human-readable strings. /// /// ## `Option` /// /// For an `Option` field, the setter accepts both `T` and `Option`. /// It does so by requiring that the argument implement traits as follows: /// /// | Field type | Setter arg trait | Implemented by | /// | -------------------- | ------------------------------------ | -------------- | /// | `Option` | [`OptionStringOrStr`] | `String, &str, Option, Option<&str>` | /// | `Option>` | [`OptionPossiblyBoundsChecked`]`` | `T, NonZero, Option, Option> `| /// | `Option` | [`PossiblyOption`]`` | `T, Option` | /// /// ## `impl Buildable` /// /// Any type that implements buildable must either use a the [`sub_builder`] attribute, /// or opt out of it with [`no_sub_builder`] or [`no_magic`]. /// /// ## `Vec`, `HashSet`, `BTreeSet` /// /// These types require you to either use the [`list`] attribute, /// or to opt out of it with [`no_sub_builder`] or [`no_magic`]. /// /// ## `HashMap`, `BTreeMap` /// /// These types require you to either use the [`map`] attribute, /// or to opt out of it with [`no_sub_builder`] or [`no_magic`]. /// /// ## `Option` /// /// This type will cause a compile-time error unless you set [`no_magic`], /// since there is not actually a way to /// set a None in toml (other than omitting the value). /// /// > (TODO: Doesn't) that logic apply to _all_ Option types? /// /// ## Container of `Duration`. /// /// These are currently disallowed unless you provide [`no_magic`], /// and will cause a compile-time error, /// since we cannot apply our regular serde magic. /// /// [`StringOrStr`]: crate::setter_traits::StringOrStr /// [`OptionStringOrStr`]: crate::setter_traits::OptionStringOrStr /// [`PossiblyBoundsChecked`]: crate::setter_traits::PossiblyBoundsChecked /// [`OptionPossiblyBoundsChecked`]: crate::setter_traits::OptionPossiblyBoundsChecked /// [`PossiblyOption`]: crate::setter_traits::PossiblyOption /// [`NonZero`]: std::num::NonZero /// [`tor_config(no_magic)`]: crate::derive::doc_ref_attrs#fmeta:no_magic /// [`list`]: crate::derive::doc_ref_attrs#fmeta:list /// [`map`]: crate::derive::doc_ref_attrs#fmeta:map /// [`sub_builder`]: crate::derive::doc_ref_attrs#fmeta:sub_builder /// [`no_sub_builder`]: crate::derive::doc_ref_attrs#fmeta:no_sub_builder /// [`no_magic`]: crate::derive::doc_ref_attrs#fmeta:no_magic pub mod doc_magic_types {} // TODO: // - Can I replace cfg() with a single FeatureNotSupported type, or a family of such types? // (See #2298.) // - derive-deftly#109 would let us have better syntax for tmeta:attr, fmeta:attr, and fmeta:serde. // - We must decide before merging whether we actually _want_ to always accept T // for setters on `NonZero`. There are some places in our code where we do this now: // so if we were to stop doing so, we'd be breaking backward compat in the `arti` crate. // But that doesn't mean we need to continue to do so everywhere. // (See #2296) use derive_deftly::define_derive_deftly; /// Values used by the tor_config derive_deftly template. #[doc(hidden)] pub mod exports { pub use super::{ ShouldBeCaughtAsSerdeSpecialCase, ShouldBeCaughtAsSpecialCase, ShouldNotBeUsed, ShouldUseListBuilder, ShouldUseMapBuilder, assert_not_impl, bld_magic_check_type, bld_magic_cvt, bld_magic_setter_arg_type, bld_magic_setter_cvt, bld_magic_setter_docs, bld_magic_type, list_element, normalize_and_invoke, strip_option, }; pub use crate::flatten::derive_deftly_template_Flattenable; pub use crate::{ ConfigBuildError, define_list_builder_accessors, define_list_builder_helper, define_map_builder, extend_builder::{ExtendBuilder, ExtendStrategy}, impl_standard_builder, load::Buildable as BuildableTrait, load::Builder as BuilderTrait, }; pub use derive_deftly::Deftly; pub use figment; pub use humantime_serde; pub use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub use serde_value; pub use std::{ clone::Clone, convert::{AsRef, Into, TryInto}, default::Default, fmt::Debug, option::Option, result::Result, }; pub use tracing; pub use void; } /// Module to import in order to use the tor_config template. pub mod prelude { pub use super::derive_deftly_template_TorConfig; } /// Helper to implement `strip_option`: Takes a single argument whose type is an Option, /// and yields T. #[doc(hidden)] #[macro_export] macro_rules! strip_option { { $($($(::)? std::)? option::)? Option $(::)? < $t:ty > } => { $t }; { $t:ty } => { compile_error!{"'strip_option' only works on a type that is an Option"} } } pub use strip_option; /// Helper: Takes a @command name, a {type}, and a list of arguments. /// Determines which macro corresponds to the type, /// and invokes that macro with arguments: @command {type} args. /// /// The macros corresponding to types are [`string_typemagic`] for `String`, /// [`nonzero_typemagic`] for `NonZero*`, /// and [`no_typemagic`] for everything else. /// /// Recognized @commands are: /// - `@build_type {ty}` - The type that should be used as `T` for the builder field's `Option`. /// - `@setter_arg_type {ty}` - The type that the setter should take as an argument. /// - `@setter_cvt {ty} {e}`- Code to run inside the setter to convert {e} into a `@build_type {ty}` /// - `@build_field {ty} {e} {name}` - Code to convert `@build_type {ty}` into {ty}. Uses the /// field name `{fname}` to generate errors. /// - `@check_type {ty}` - Make sure that it is not a bug for this type to have reached this position. /// Give a compile-time error if it is. /// - `@setter_docs {ty}` - A string to add to the setter documentation. #[doc(hidden)] #[macro_export] macro_rules! normalize_and_invoke { // HEY YOU! DON'T ADD OR REMOVE ANY TYPES WITHOUT READING THE COMMENT BELOW! { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? num:: )? NonZero $(::)? < $t:ty > > } $($args:tt)*} => { $crate::derive::opt_nz_typemagic! { @$cmd {$t} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? num:: )? NonZeroU8 > } $($args:tt)*} => { $crate::derive::opt_nz_typemagic! { @$cmd {u8} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? num:: )? NonZeroU16 > } $($args:tt)*} => { $crate::derive::opt_nz_typemagic! { @$cmd {u16} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? num:: )? NonZeroU32 > } $($args:tt)*} => { $crate::derive::opt_nz_typemagic! { @$cmd {u32} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? num:: )? NonZeroU64 > } $($args:tt)*} => { $crate::derive::opt_nz_typemagic! { @$cmd {u64} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? num:: )? NonZeroU128 > } $($args:tt)*} => { $crate::derive::opt_nz_typemagic! { @$cmd {u128} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $( $($(::)? std::)? string:: )? String > } $($args:tt)*} => { $crate::derive::opt_str_typemagic! { @$cmd $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? option:: )? Option $(::)? < $t:ty > } $($args:tt)*} => {$crate::derive::opt_other_typemagic!{@$cmd {$t} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? num:: )? NonZero $(::)? < $t:ty >} $($args:tt)*} => { $crate::derive::nonzero_typemagic!{ @$cmd {$t} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? num:: )? NonZeroU8} $($args:tt)*} => { $crate::derive::nonzero_typemagic!{ @$cmd {u8} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? num:: )? NonZeroU16} $($args:tt)*} => { $crate::derive::nonzero_typemagic!{ @$cmd {u16} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? num:: )? NonZeroU32} $($args:tt)*} => { $crate::derive::nonzero_typemagic!{ @$cmd {u32} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? num:: )? NonZeroU64} $($args:tt)*} => { $crate::derive::nonzero_typemagic!{ @$cmd {u64} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? num:: )? NonZeroU128} $($args:tt)*} => { $crate::derive::nonzero_typemagic!{ @$cmd {u128} $($args)* } }; { @$cmd:ident {$( $($(::)? std::)? string:: )? String} $($args:tt)*} => { $crate::derive::string_typemagic! { @$cmd $($args)* } }; { @$cmd:ident {$($t:tt)+} $($args:tt)*} => { $crate::derive::no_typemagic! { @$cmd {$($t)+} $($args)* } }; // HEY YOU! DON'T ADD OR REMOVE TYPES WITHOUT READING THIS COMMENT! // // 1. You need to make sure that every type handled by this macro implements // `ShouldBeCaughtAsASpecialCase`. // 2. Make sure to document the behavior in `doc_magic_types` above. } pub use normalize_and_invoke; /// Implement type magic for types that aren't at all special. /// /// See [`normalize_and_invoke`] for details. #[doc(hidden)] #[macro_export] macro_rules! no_typemagic { { @build_type {$t:ty} } => { $t }; { @setter_arg_type {$t:ty}} => { $t }; { @setter_cvt {$t:ty} {$e:expr} } => { $e }; { @build_field {$t:ty} {$e:expr} {$fname:expr}} => { $e.clone() }; { @check_type {$t:ty} } => { $crate::derive::exports::assert_not_impl!( [type_was_not_correctly_identified_as_a_TorConfig_special_case] $t: $crate::derive::ShouldBeCaughtAsSpecialCase ); }; { @setter_docs {$t:ty} } => { "" }; } pub use no_typemagic; /// Implement type magic for `String`. /// /// See [`normalize_and_invoke`] for details. #[doc(hidden)] #[macro_export] macro_rules! string_typemagic { { @build_type } => { String }; { @setter_arg_type } => { impl $crate::setter_traits::StringOrStr }; { @setter_cvt {$e:expr} } => { $e.to_string() }; { @build_field {$e:expr} {$fname:expr}} => { $e.clone() }; { @check_type } => {}; { @setter_docs } => { "\nFor convenience, this function accepts both `String` and `&str`.\n" }; } pub use string_typemagic; /// Implement type magic for `NonZero*`. /// /// See [`normalize_and_invoke`] for details. #[doc(hidden)] #[macro_export] macro_rules! nonzero_typemagic { { @build_type {$t:ty} } => { $t }; { @setter_arg_type {$t:ty} } => { impl $crate::setter_traits::PossiblyBoundsChecked<$t> }; { @setter_cvt {$t:ty} {$e:expr} } => { $e.to_unchecked() }; { @build_field {$t:ty} {$e:expr} {$fname:expr}} => { $e.map(|v| v.try_into().map_err(|_| $crate::ConfigBuildError::Invalid { field: $fname.to_string(), problem: "value not allowed to be zero".to_string() })).transpose()? }; { @check_type {$t:ty} } => {}; { @setter_docs {$t:ty} } => { concat!("\nFor convenience, this function accepts both `", stringify!($t), "` and `NonZero<", stringify!($t), ">`.\n" ) }; } pub use nonzero_typemagic; /// Implement type magic for `Option` /// /// See [`normalize_and_invoke`] for details. #[doc(hidden)] #[macro_export] macro_rules! opt_nz_typemagic { { @build_type {$t:ty} } => { Option<$t> }; { @setter_arg_type {$t:ty} } => { impl $crate::setter_traits::OptionPossiblyBoundsChecked<$t> }; { @setter_cvt {$t:ty} {$e:expr} } => { $e.to_option_unchecked() }; { @build_field {$t:ty} {$e:expr} {$fname:expr}} => { match $e { Some(Some(v)) => match v.try_into() { Ok(n) => Some(Some(n)), Err(_) => return Err( $crate::ConfigBuildError::Invalid { field: $fname.to_string(), problem: "value not allowed to be zero".to_string() }) } Some(None) => Some(None), None => None, } }; { @check_type {$t:ty} } => {}; { @setter_docs {$t:ty} } => { concat!("\nFor convenience, this function accepts `", stringify!($t), "`, `NonZero<", stringify!($t), ">`, `Option<", stringify!($t), ">`, and `Option`.\n") }; } pub use opt_nz_typemagic; /// Implement type magic for `Option` /// /// See [`normalize_and_invoke`] for details. #[doc(hidden)] #[macro_export] macro_rules! opt_str_typemagic { { @build_type } => { Option }; { @setter_arg_type } => { impl $crate::setter_traits::OptionStringOrStr }; { @setter_cvt {$e:expr} } => { $e.to_option_string() }; { @build_field {$e:expr} {$fname:expr}} => { $e.clone() }; { @check_type } => {}; { @setter_docs } => { "\nFor convenience, this function accepts `String`, `&str`, \ `Option`, and `Option<&str>`.\n" }; } pub use opt_str_typemagic; /// Implement type magic for `Option` /// /// See [`normalize_and_invoke`] for details. #[doc(hidden)] #[macro_export] macro_rules! opt_other_typemagic { { @build_type {$t:ty} } => { Option<$t> }; { @setter_arg_type {$t:ty} } => { impl $crate::setter_traits::PossiblyOption<$t> }; { @setter_cvt {$t:ty} {$e:expr} } => { $e.to_option() }; { @build_field {$t:ty} {$e:expr} {$fname:expr}} => { $e.clone() }; { @check_type {$t:ty} } => { $crate::derive::exports::assert_not_impl!( [type_was_not_correctly_identified_as_a_TorConfig_Option_special_case] $t: $crate::derive::ShouldBeCaughtAsSpecialCase ); }; { @setter_docs {$t:ty} } => { concat!("\nFor convenience, this function accepts both `", stringify!($t), "` and `Option<", stringify!($T), ">`\n.") }; } pub use opt_other_typemagic; /// Helper: Expand to the type `T` that a field of type `{$t}` /// should have in the builder struct's Option. #[doc(hidden)] #[macro_export] macro_rules! bld_magic_type { { $($t:tt)+ } => { $crate::derive::normalize_and_invoke!{ @build_type {$($t)+} } }; } pub use bld_magic_type; /// Helper: Expand to the type `T` that a setter should take for a field of type `{$t}` #[doc(hidden)] #[macro_export] macro_rules! bld_magic_setter_arg_type { { $($t:tt)+ } => { $crate::derive::normalize_and_invoke!{ @setter_arg_type {$($t)+} } }; } pub use bld_magic_setter_arg_type; /// Helper: Expand to the code that should be used to convert `{e}` into the type /// `bld_magic_type!{$t}`. #[doc(hidden)] #[macro_export] macro_rules! bld_magic_setter_cvt { { {$e:expr} {$($t:tt)+}} => { $crate::derive::normalize_and_invoke!{ @setter_cvt {$($t)+} {$e} } }; } pub use bld_magic_setter_cvt; /// Helper: Expand to the code that should be used to convert `{e}` from type /// `bld_magic_type!{$t}` into $t. Uses the field name `{fname} to generate errors. #[doc(hidden)] #[macro_export] macro_rules! bld_magic_cvt { { {$e:expr} {$fname:expr} {$($t:tt)+}} => { $crate::derive::normalize_and_invoke!{ @build_field {$($t)+} {$e} {$fname} } }; } pub use bld_magic_cvt; /// Helper: Expand to the code that will make sure that the type `{t}` /// can be handled correctly by these macros. /// The code will cause a compile-time error on failure. #[doc(hidden)] #[macro_export] macro_rules! bld_magic_check_type { { {$($t:tt)+}} => { $crate::derive::normalize_and_invoke!{ @check_type {$($t)+} } }; } pub use bld_magic_check_type; /// Helper: Expand to a documentation string describing any type-based magic for /// the setter for this type. #[doc(hidden)] #[macro_export] macro_rules! bld_magic_setter_docs { { {$($t:tt)+} } => { $crate::derive::normalize_and_invoke!{ @setter_docs {$($t)+} } }; } pub use bld_magic_setter_docs; /// Helper for sealing traits below. mod seal { /// Used to seal ShouldBeCaughtAsSpecialCase. pub trait SealSpecialCase {} /// Used to seal ShouldBeCaughtAsSerdeSpecialCase. pub trait SealSerdeSpecialCase {} /// Used to seal ShouldUseListBuilder. pub trait SealUseListBuilder {} /// Used to seal ShouldUseMapBuilder pub trait SealUseMapBuilder {} /// Used to seal ShouldNotBeUsed pub trait SealShouldNotBeUsed {} } /// Implement each `trait` for every comma-separated type in `types`, with an empty body. macro_rules! impl_many { { $($ty:ty),+ : $trait:ty } => { $( impl $trait for $ty {} )+ }; { $($ty:ty),+ : $trait:ty , $($more:tt)+} => { impl_many!{ $($ty),+ : $trait } impl_many!{ $($ty),+ : $($more)+ } }; } /// A trait implemented by all the types that `normalize_and_invoke` does anything magic for. /// /// We use this trait to detect cases that normalize_and_invoke should have handled, but didn't-- /// probably because the type was an alias. pub trait ShouldBeCaughtAsSpecialCase: seal::SealSpecialCase {} impl_many! { std::num::NonZero, std::num::NonZero, std::num::NonZero, std::num::NonZero, std::num::NonZero, String : seal::SealSpecialCase, ShouldBeCaughtAsSpecialCase } impl seal::SealSpecialCase for Option {} impl ShouldBeCaughtAsSpecialCase for Option {} /// A trait implemented by all the types that should receive automatic serde magic handling. /// /// We use this trait to detect cases that the tor_config derive macro should have caught, /// but didn't-- probably because the type was an alias. pub trait ShouldBeCaughtAsSerdeSpecialCase: seal::SealSerdeSpecialCase {} impl_many! { std::time::Duration : seal::SealSerdeSpecialCase, ShouldBeCaughtAsSerdeSpecialCase } /// A trait implemented by the types for which we should recommend the use of a /// list builder. pub trait ShouldUseListBuilder: seal::SealUseListBuilder { /// The element type of this list. type Element; } impl seal::SealUseListBuilder for Vec {} impl seal::SealUseListBuilder for std::collections::HashSet {} impl seal::SealUseListBuilder for std::collections::BTreeSet {} impl ShouldUseListBuilder for Vec { type Element = T; } impl ShouldUseListBuilder for std::collections::HashSet { type Element = T; } impl ShouldUseListBuilder for std::collections::BTreeSet { type Element = T; } /// Helper to implement `list_builder`: find the element type for a list-like object. #[doc(hidden)] #[macro_export] macro_rules! list_element { { $($($(::)? std::)? vec::)? Vec $(::)? < $t:ty > } => { $t }; { $($($(::)? std::)? collections::)? HashSet $(::)? < $t:ty > } => { $t }; { $($($(::)? std::)? collections::)? BTreeSet $(::)? < $t:ty > } => { $t }; { $t:ty } => { compile_error!{"'list_builder' only works on Vec, HashSet, or BTreeSet."} } } pub use list_element; /// A trait implemented by the types for which we should recommend the use of a /// map builder. pub trait ShouldUseMapBuilder: seal::SealUseMapBuilder { /// The corresponding type to use inside this map builder type BuilderMap; } impl seal::SealUseMapBuilder for std::collections::HashMap where T: crate::load::Buildable { } impl seal::SealUseMapBuilder for std::collections::BTreeMap where T: crate::load::Buildable { } impl ShouldUseMapBuilder for std::collections::HashMap where T: crate::load::Buildable, { type BuilderMap = std::collections::HashMap; } impl ShouldUseMapBuilder for std::collections::BTreeMap where T: crate::load::Buildable, { type BuilderMap = std::collections::BTreeMap; } /// A trait implemented by types that we shouldn't actually use as fields in a configuration. pub trait ShouldNotBeUsed: seal::SealShouldNotBeUsed {} impl_many! { // This is unsuitable, since there is no actual way to set a value to `none` in Toml. // Use Duration with a default instead. Option, Option> : seal::SealShouldNotBeUsed, ShouldNotBeUsed } /// Declare that types shouldn't be used in a collection (because they would want magic /// serde handling, but that isn't implemented). macro_rules! should_not_be_used_in_collection { { $($t:ty),* $(,)?} => { $( impl_many!{ Vec<$t>, std::collections::BTreeSet<$t>, std::collections::HashSet<$t> : seal::SealShouldNotBeUsed, ShouldNotBeUsed } impl seal::SealShouldNotBeUsed for std::collections::HashMap {} impl ShouldNotBeUsed for std::collections::HashMap {} impl seal::SealShouldNotBeUsed for std::collections::BTreeMap {} impl ShouldNotBeUsed for std::collections::BTreeMap {} )* } } should_not_be_used_in_collection! { std::time::Duration, } /// Give a compile time error if the type $t implements $trait. /// /// Includes the identifier $rule in the error message, to help the user diagnose /// the problem. (This is the main difference between this macro and the one in /// `static_assertions`.) /// /// # Example (Succeeding.) /// /// ``` /// // No error will occur; String is not Buildable. /// tor_config::derive::assert_not_impl!{ /// [copy_is_forbidden_here] String : tor_config::load::Buildable /// } /// ``` /// /// ```compile_fail /// // Compile-time error _is_ given; String implements Clone. /// tor_config::derive::assert_not_impl!{ /// [clone_is_forbidden_here] String : Clone /// } /// ``` #[macro_export] macro_rules! assert_not_impl { {[$rule:ident] $t:ty : $trait:path } => { const _ : () = { #[allow(dead_code, non_camel_case_types)] trait $rule { fn item(); } impl $rule<()> for $t { fn item() {}} struct Invalid; impl $rule for T { fn item() {} } let _ = <$t as $rule<_>>::item; }; } } pub use assert_not_impl; define_derive_deftly! { /// Define a builder type for a given type, with settings appropriate to participate in the Arti /// build system. /// /// See [module documentation](crate::derive) for more information and usage instructions. export TorConfig beta_deftly, for struct: #[allow(unused_imports)] use $crate::derive::exports as $<__tor_config_exports__ $tname>; // ------------------- // Common definitions and aliases. // Location of exports for this macro. ${define E {$crate::derive::exports}} // Location of exports for this macro, as a string. // // TODO $crate: I would prefer to use ${concat $crate "::derive::exports"} instead, // but that won't work, since $crate gets expanded at the wrong time. // See // https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/132#note_3288325 // for discussion of the workaround used here. ${define EX ${concat "__tor_config_exports__" $tname}} // Current field name as string. ${define FNAME { ${concat $fname} }} // Name of the build method. ${define BLD_NAME { ${tmeta(tor_config(build_fn(name))) as ident, default build} } } // ------------------- // Definitions and aliases for defining the builder struct. // True if the field type appears to be `std::time::Duration` // // (This can't be done with the regular "magic" macro system, // since it needs to be used in expansions that produce a field attribute. // macro_rules! macros can't appear in a field-attribute position. // There _are_ workarounds here, which I'll consider while // reafactoring the magic system.) ${defcond F_IS_DURATION any(approx_equal({$ftype}, {Duration}), approx_equal({$ftype}, {time::Duration}), approx_equal({$ftype}, {std::time::Duration}), approx_equal({$ftype}, {::std::time::Duration})) } // True if the field type should receive magic handling with serde. ${defcond F_SERDE_MAGIC any(F_IS_DURATION)} // Condition: True unless Serialize and Deserialize have both been disabled. ${defcond SERDE not(all(tmeta(tor_config(no_serialize_trait)), tmeta(tor_config(no_deserialize_trait)))) } // Expands to any attributes that should be applied to the current builder field // based on magic type behavior. ${define BLD_MAGIC_ATTRIBUTES ${if fmeta(tor_config(no_magic)) { } else if SERDE { ${select1 F_IS_DURATION { ${if SERDE { #[serde(with = ${concat $EX "::humantime_serde::option"})] }} } // HEY YOU! DON'T ADD ANY MORE MAGIC SERDE TYPES HERE WITHOUT READING THIS! // 1. You need to add the condition for your type to `F_SERDE_MAGIC`, // and you need to make sure that your type implements // `ShouldBeCaughtAsSerdeSpecialCase`. // 2. You need to document the behavior in `doc_magic_types`. else { ${if F_SERDE_MAGIC { ${error "Type should receive magic handling with serde, but we failed to apply any."} }} // No magic needed. }} }} } // For each list_builder field: the names of the list builder // type to define. ${define F_LST_BLD_TYPE { $< ${fmeta(tor_config(list(listtype))) as ty, default ${paste $tname ${upper_camel_case $fname} List} } Builder>} } // For each map_builder field: the names of the map builder // type to define. ${define F_MAP_TYPE { ${paste // See derive_deftly#138 for this $paste. ${fmeta(tor_config(map(maptype))) as ty, default ${paste $tname ${upper_camel_case $fname} Map}} }}} ${define F_MAP_BLD_TYPE { $< $F_MAP_TYPE Builder > }} // Expands to $ftype of the current builder field, as modified by type magic. // (Most everybody should use $BLD_FTYPE instead.) ${define BLD_MAGIC_FTYPE { ${if fmeta(tor_config(no_magic)) { $ftype } else { $E::bld_magic_type!($ftype) }} }} // Expands to the type of the field within the builder. ${define BLD_FTYPE { ${if fmeta(tor_config(field(ty))) { ${fmeta(tor_config(field(ty))) as ty} } else if fmeta(tor_config(sub_builder)) { $< $ftype Builder > } else if fmeta(tor_config(list)) { $F_LST_BLD_TYPE } else if fmeta(tor_config(map)) { $F_MAP_BLD_TYPE } else { $E::Option<$BLD_MAGIC_FTYPE> }} }} // Expands to the error type for this builder. ${define ERR ${tmeta(tor_config(build_fn(error))) as ty, default {$E::ConfigBuildError}}} // If the current field is conditionally present, expands to the `cfg` // attribute we should apply to it. ${define IF_CFG { // TODO: Infelicity: It would be nice if this didn't have to take "cfg" as a string. // See https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/56 ${if fmeta(tor_config(cfg)) { #[cfg( ${fmeta(tor_config(cfg)) as token_stream} )] }} }} // Visibility for the builder type. // // TODO: d-d has no ` .. as vis`, which is what really want. // https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/137 // (also, below) ${define BLD_TVIS ${tmeta(tor_config(vis)) as token_stream, default $tvis} } // Visibility for the current field in the builder type. ${define BLD_FVIS ${fmeta(tor_config(field(vis))) as token_stream, default {} } } // True if we want to derive Flattenable for the builder type ${defcond DD_FLATTENABLE_ON_BUILDER all(not(tmeta(tor_config(no_deserialize_trait))), not(tmeta(tor_config(no_flattenable_trait))) )} // Expands to the visibility for the current setter/accessor, // and for any types that we generate for it to expose. ${define SETTER_VIS { ${fmeta(tor_config(setter(vis))) as token_stream, default $BLD_TVIS} }} // ------------------- // Invoke checks for our type-handling macros. $( ${if fmeta(tor_config(no_magic)) { // If we're disabling the magic, this is fine. } else { // Make sure that, for every other, it gets matched by the right case of // normalize_and_invoke. // // (We do this because our type checking in normalize_and_invoke is imperfect, // and can't detect aliases.) $E::bld_magic_check_type!({$ftype}); }} ${if all(not(F_SERDE_MAGIC), not(fmeta(tor_config(no_magic))) ) { // If we don't get special handling from serde, make sure that the type doesn't actually // need it. // // (We do this because our type checking in F_IS_DURATION is imperfect and can't // detect) $E::assert_not_impl!( [type_was_not_correctly_identified_as_a_TorConfig_serde_special_case] $ftype: $E::ShouldBeCaughtAsSerdeSpecialCase ); }} ) // ------------------- // Check for types that don't make sense in a configuration. $( ${when not(fmeta(tor_config(no_magic)))} $E::assert_not_impl!( [field_type_not_suitable_for_configuration] $ftype: $E::ShouldNotBeUsed ); ) // ------------------- // Check for missing invocations of sub_builder, list, or map. $( ${when not(any( fmeta(tor_config(sub_builder)), fmeta(tor_config(no_sub_builder)), fmeta(tor_config(no_magic)), fmeta(tor_config(build)), fmeta(tor_config(try_build)), ))} $E::assert_not_impl!( [missing_sub_builder_declaration_for_Buildable_field] $ftype: $E::BuildableTrait ); ) $( ${when not(any( fmeta(tor_config(no_sub_builder)), fmeta(tor_config(no_magic)), fmeta(tor_config(list)), ))} $E::assert_not_impl!( [field_should_use_list_builder_or_opt_out] $ftype: $E::ShouldUseListBuilder ); ) $( ${when not(any( fmeta(tor_config(no_sub_builder)), fmeta(tor_config(no_magic)), fmeta(tor_config(map)), ))} $E::assert_not_impl!( [field_should_use_map_builder_or_opt_out] $ftype: $E::ShouldUseMapBuilder ); ) // ------------------- // Define the builder type. #[doc = ${concat "A builder to create an instance of [`" $tname "`].\n"}] #[derive($E::Default, $E::Clone, $E::Debug, $E::Deftly)] ${if not(tmeta(tor_config(no_deserialize_trait))) { #[derive($E::Deserialize)] }} ${if not(tmeta(tor_config(no_serialize_trait))) { #[derive($E::Serialize)] }} ${if DD_FLATTENABLE_ON_BUILDER { #[derive_deftly($E::Flattenable)] }} // TODO: drop requirement this be a string. // https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/56 ${ if tmeta(tor_config(attr)) { #[ ${tmeta(tor_config(attr)) as token_stream} ] }} #[allow(dead_code)] $BLD_TVIS struct $<$tname Builder><$tdefgens> where $twheres { $( ${when not(fmeta(tor_config(skip)))} // TODO: drop requirement this be a string. // https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/56 ${if fmeta(tor_config(attr)) { #[${fmeta(tor_config(attr)) as token_stream}] }} ${if SERDE { #[serde(default)] }} // TODO: drop requirement this be a string. // https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/56 ${ if fmeta(tor_config(serde)) { #[ serde( ${fmeta(tor_config(serde)) as token_stream} )] }} #[doc = ${concat "In-progress value for " $fname ".\n\n" "See [`" $tname "." $fname "`]("$tname "#structfield." $fname ")"}] $BLD_MAGIC_ATTRIBUTES $IF_CFG $BLD_FVIS ${fdefine $fname} $BLD_FTYPE, ${if all(SERDE, fmeta(tor_config(cfg))) { /// Placeholder to catch attempts to use this field when /// disabled by configuration. /// /// (This uses `serde_value` to make sure that Deserialize+Serialize is not /// needlessly lossy.) #[cfg(not(${fmeta(tor_config(cfg)) as token_stream} ))] #[serde(default)] ${fdefine $fname} $E::Option<$E::serde_value::Value>, }} ) } // ------------------- // Define any list-builder or map-builder types. ${define BUILD_LIST_ELEMENT { ${select1 fmeta(tor_config(list(element(build)))) { |v| v.${fmeta(tor_config(list(element(build)))) as ident, default build}() } fmeta(tor_config(list(element(clone)))) { |v| Ok(v.clone()) } else { ${error "With list, must specify list(element(clone)) or list(element(build))"} }} }} ${define BLD_LIST_ELT_TYPE { ${select1 fmeta(tor_config(list(element(build)))) { // TODO: Find a way to get the argument types for the setters be // nicer. // // It would be cool if we could paste "Builder" on to the end of the // output of $E::list_element, but that doesn't work. <<$ftype as $E::ShouldUseListBuilder>::Element as $E::BuildableTrait>::Builder } fmeta(tor_config(list(element(clone)))) { // We could use ShouldUseListBuilder::Element here, but the declared // argument types for the setters would be a bit nasty. $E::list_element!{ $ftype } } else { ${error "With list, must specify list(element(clone)) or list(element(build))"} }} }} $( ${when fmeta(tor_config(list))} $E::define_list_builder_helper! { $BLD_FVIS struct $F_LST_BLD_TYPE { $BLD_FVIS $fname: [ $BLD_LIST_ELT_TYPE ], } built: $ftype = $fname; default = ${fmeta(tor_config(default)) as expr}; item_build: $BUILD_LIST_ELEMENT; } ) $( ${when fmeta(tor_config(map))} $E::define_map_builder! { $SETTER_VIS struct $F_MAP_BLD_TYPE => $BLD_FVIS type $F_MAP_TYPE = { map: $ftype, builder_map: <$ftype as $E::ShouldUseMapBuilder>::BuilderMap, } defaults: ${fmeta(tor_config(default)) as expr}; } ) // ------------------- // Definitions to implement setter/accessor methods. // Expands to the name of the setter/accessor for the current field. ${define SETTER_NAME { ${fmeta(tor_config(setter(name))) as ident, default $fname} }} // Expands the declared type that the setter should take as its argument. ${define SETTER_INPUT_TYPE { ${select1 fmeta(tor_config(setter(into))) { impl $E::Into<$ftype> } fmeta(tor_config(setter(try_into))) { SetterArg } fmeta(tor_config(setter(strip_option))) { $E::strip_option!{$ftype} } else { ${if fmeta(tor_config(no_magic)) { $ftype } else { $E::bld_magic_setter_arg_type!{$ftype} } }} }}} // Expands to the generics (if any) that the setter should take. ${define SETTER_GENS { ${if fmeta(tor_config(setter(try_into))) { > } else { }} }} // Expands to the expression that the setter should return. ${define SETTER_RETURN { ${if fmeta(tor_config(setter(try_into))) { Ok(self) } else { self }} }} // Expands to the declared return type of the setter. ${define SETTER_RETURN_TYPE { ${if fmeta(tor_config(setter(try_into))) { $E::Result<&mut Self, SetterArg::Error> } else { &mut Self }} }} // Expands to a string that we should add to the documentation // to explain the default value of the current field. ${define DFLT_DOC { ${select1 fmeta(tor_config(sub_builder)) { "" } // TODO: perhaps we should document _something_ about build and try_build, // even though we can't document any default. fmeta(tor_config(build)) { "" } fmeta(tor_config(try_build)) { "" } fmeta(tor_config(default)) { ${concat "If no value is provided for `" $fname "`, " "[`build`](Self::build) will use `" ${fmeta(default) as str, default "Default::default()"} "`." }} fmeta(tor_config(no_default)) { ${concat "If no value is provided for `" $fname "`, " "[`build`](Self::build) will fail with an error."} } else { ${error "Every field must have default, no_default, try_build, build, or sub_builder."} } } }} // Expands to a definition of the setter function for the current field. ${define SET_FN { #[doc = ${concat "Provide a value for `" $fname "`.\n\n"}] #[doc = $DFLT_DOC] ${if not(fmeta(tor_config(no_magic))) { #[doc = $E::bld_magic_setter_docs!{ { $ftype } } ] }} #[doc = ${concat "\n\n## " $fname "\n\n" }] ${fattrs doc} $IF_CFG $SETTER_VIS fn $SETTER_NAME $SETTER_GENS (&mut self, val: $SETTER_INPUT_TYPE) -> $SETTER_RETURN_TYPE { ${select1 fmeta(tor_config(setter(into))) { self.$fname = Some(val.into()); } fmeta(tor_config(setter(try_into))) { self.$fname = Some(val.try_into()?); } fmeta(tor_config(setter(strip_option))) { self.$fname = Some(Some(val)); } else { ${if fmeta(tor_config(no_magic)) { self.$fname = Some(val); } else { self.$fname = Some($E::bld_magic_setter_cvt!({val} {$ftype})); }} } } $SETTER_RETURN } }} ${define F_SUB_BUILDER_TYPE ${if fmeta(tor_config(map)) { $F_MAP_BLD_TYPE } else { $<$ftype Builder> }} } // Expands to a declaration for the sub-builder accessor function for the current field. ${define ACCESS_SUBBUILDER_FN { #[doc = ${concat "Return a mutable reference to the inner builder for `" $fname "`.\n\n" "## " $fname "\n\n" }] ${fattrs doc} $IF_CFG $SETTER_VIS fn $fname(&mut self) -> &mut $F_SUB_BUILDER_TYPE { &mut self.$fname } }} // ------------------- // Define the setter/accessors methods. #[allow(dead_code)] impl<$tgens> $<$ttype Builder> where $twheres { $( ${if any(fmeta(tor_config(setter(skip))), fmeta(tor_config(skip)), fmeta(tor_config(list))) { // generate nothing. } else if any(fmeta(tor_config(sub_builder)), fmeta(tor_config(map))) { $ACCESS_SUBBUILDER_FN } else { $SET_FN }} ) } $( ${when fmeta(tor_config(list))} $E::define_list_builder_accessors!{ struct $<$tname Builder> { $SETTER_VIS $fname : [ $BLD_LIST_ELT_TYPE ], } } ) // ------------------- // Definitions and helpers for the build method. // Expands to the name of the function to use to build the sub-builder for the current field. ${define SUB_BUILDER_BUILD_FN { ${fmeta(tor_config(sub_builder(build_fn))) as path, default build} }} // Expands to an expression of type Option<$ftype> for the current field, // taking type-based magic into account. ${define BLD_MAGIC_CVT { ${if fmeta(tor_config(no_magic)) { self.$fname.clone() } else { $E::bld_magic_cvt!({self.$fname} {${concat $fname}} {$ftype}) }} }} // Expands to the closure we run on a missing field to get the error type. ${define BLD_MISSING_FIELD { ${tmeta(tor_config(missing_field)) as expr, default { |name_of_missing_field: &str| $ERR::MissingField { field: name_of_missing_field.into() } }} }} // Expands to an expression for building the current field, // and returning an appropriate error if there was a problem. ${define BUILD_FIELD { ${select1 any(fmeta(tor_config(sub_builder)), fmeta(tor_config(list)), fmeta(tor_config(map))) { self.$fname.$SUB_BUILDER_BUILD_FN().map_err(|e| e.within($FNAME))? } all(fmeta(tor_config(default)), not(any(fmeta(tor_config(list)), fmeta(tor_config(map))))) { $BLD_MAGIC_CVT.unwrap_or_else( || ${fmeta(tor_config(default)) as expr, default {Default::default()}}) } fmeta(tor_config(build)) { (${fmeta(tor_config(build)) as expr})(self) } fmeta(tor_config(try_build)) { (${fmeta(tor_config(try_build)) as expr})(self)? } fmeta(tor_config(no_default)) { $BLD_MAGIC_CVT.ok_or_else( || { ($BLD_MISSING_FIELD)(stringify!($fname)) } )? } else { ${error "Every field must have default, no_default, try_build, build, or sub_builder."} } } }} // Expands to the visibility of the build method. ${define BLD_FN_VIS { ${tmeta(tor_config(build_fn(vis))) as token_stream, default $BLD_TVIS} }} // ------------------- // Define the build method and the new() method. #[allow(dead_code)] impl<$tgens> $<$ttype Builder> where $twheres { /// Return a new builder object. $BLD_TVIS fn new() -> Self { Self::default() } #[doc = ${concat "Try to construct a new [`" $tname "`] from the fields set in this builder.\n\n" "Return an error if any required field is missing, or is set to something invalid.\n" }] $BLD_FN_VIS fn $BLD_NAME(&self) -> $E::Result<$ttype, $ERR> { // Call pre_build as appropriate. ${if tmeta(tor_config(pre_build)) { let () = ${tmeta(tor_config(pre_build)) as path}(self)?; }} // Warn if any configured-out option was provided. $( ${if fmeta(tor_config(cfg)) { #[cfg(not( ${fmeta(tor_config(cfg)) as token_stream} ))] if self.$fname.is_some() { $E::tracing::warn!( ${concat "Ignored configuration for '" $fname "'. This option has no effect unless the program is built " ${fmeta(tor_config(cfg_desc)) as str} "'"} ) } }} ) // Construct the configuration struct. let result = $tname { $( $IF_CFG $fname: $BUILD_FIELD , ${if fmeta(tor_config(cfg)) { #[cfg(not( ${fmeta(tor_config(cfg)) as token_stream} ))] $fname: $E::Default::default(), }} ) }; // Call the post_build function to transform the result. ${if tmeta(tor_config(post_build)) { let result = ${tmeta(tor_config(post_build)) as path}(result)?; }} Ok(result) } } // ------------------- // Add a builder() method to the configuration type. // // NOTE: This and some other code below is redundant with impl_standard_builder!(). // I'm not using that trait here because complying with its input format is rather // baroque, and it's easier just to do it ourselves. impl<$tgens> $ttype where $twheres { #[doc = ${concat "Return a new [`" $tname " Builder`] to construct an instance of this type."}] #[allow(dead_code)] $tvis fn builder() -> $<$ttype Builder> { $<$ttype Builder>::default() } } // ------------------- // Implement `$crate::load::Builder` for the Builder type. ${if not(tmeta(tor_config(no_builder_trait))) { impl<$tgens> $E::BuilderTrait for $<$ttype Builder> where $twheres { type Built = $ttype; // We're writing it this way in case Builder::build() returns // a different Error type. #[allow(clippy::needless_question_mark)] fn build(&self) -> $E::Result<$ttype, $E::ConfigBuildError> { Ok($<$ttype Builder>::$BLD_NAME(self)?) } } }} // ------------------- // Implement $crate::extend_builder::ExtendBuiler on the builder type. // (We can't use derive_deftly_template_ExtendBuilder, since that macro was // written to apply to the configuration type and modify its builder. (!)) ${if not(tmeta(tor_config(no_extendbuilder_trait))) { impl<$tgens> $E::ExtendBuilder for $<$ttype Builder> where $twheres { #[allow(unused_variables)] fn extend_from(&mut self, other: Self, strategy: $E::ExtendStrategy) { ${for fields { ${when not(fmeta(tor_config(skip)))} ${if any(fmeta(tor_config(sub_builder)), fmeta(tor_config(list)), fmeta(tor_config(map))) { $E::ExtendBuilder::extend_from(&mut self.$fname, other.$fname, strategy); } else if fmeta(tor_config(extend_with)) { ${fmeta(tor_config(extend_with)) as expr}(&mut self.$fname, other.$fname, strategy); } else { if let Some(other_val) = other.$fname { self.$fname = Some(other_val); } }} }} } } }} // ------------------- // Implement `$crate::load::Buildable` for the configuration type. ${if not(tmeta(tor_config(no_buildable_trait))) { impl<$tgens> $E::BuildableTrait for $ttype where $twheres { type Builder = $<$ttype Builder>; fn builder() -> $<$ttype Builder> { $<$ttype Builder>::default() } } }} // ------------------- // Implement `Default` for the configuration type, in terms of the Builder. // (Unless the no_default_trait attribute was present.) ${if not(tmeta(tor_config(no_default_trait))) { impl<$tgens> $E::Default for $ttype where $twheres { fn default() -> Self { // It's okay to use unwrap; one of the test cases verifies it. $<$ttype Builder>::default().$BLD_NAME().unwrap() } } }} // ------------------ // Test module for the builder. ${if not(any( tmeta(tor_config(no_default_trait)), tmeta(tor_config(no_deserialize_trait)), tmeta(tor_config(no_test_default)))) { #[cfg(test)] mod $ { #[test] // TODO: doesn't work on generics. Do we care? If so, how should we fix? fn test_impl_default() { let def = super::$ttype::default(); let empty_config = $E::figment::Figment::new(); let builder: super::$<$ttype Builder> = empty_config.extract().unwrap(); let from_empty = builder.$BLD_NAME().unwrap(); assert_eq!(def, from_empty); } } }} } pub use derive_deftly_template_TorConfig; #[cfg(test)] mod test { // @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::mixed_attributes_style)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_time_subtraction)] #![allow(clippy::useless_vec)] #![allow(clippy::needless_pass_by_value)] //! use crate::ConfigBuildError; use assert_matches::assert_matches; use tracing_test::traced_test; /// Separate module to put config structs and their builders in, so that they aren't /// able to pick up anything we don't deliberately import. mod t { use std::{ collections::{BTreeSet, HashMap}, num::{NonZero, NonZeroU8}, time::Duration, }; // use crate::derive::prelude::*; use derive_deftly::Deftly; #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct Simple { #[deftly(tor_config(default))] pub(super) xyz: u32, #[deftly(tor_config(default = "3"))] pub(super) abc: u16, #[deftly(tor_config(build = "|_self| 6 * 7"))] pub(super) forty_two: u16, #[deftly(tor_config( try_build = "|_self| Ok::<_,crate::ConfigBuildError>(6 * 7 + 1)" ))] pub(super) forty_three: u16, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config(no_default_trait))] pub(super) struct FieldNoDefault { #[deftly(tor_config(default))] pub(super) has_default: u32, #[deftly(tor_config(no_default))] pub(super) has_no_default: u32, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config(no_default_trait))] pub(super) struct Sub { #[deftly(tor_config(sub_builder))] pub(super) simple: Simple, #[deftly(tor_config(sub_builder))] pub(super) fnd: FieldNoDefault, #[deftly(tor_config(default))] pub(super) s: String, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct Magic { #[deftly(tor_config(default = "nz(7)"))] pub(super) nzu8: NonZeroU8, #[deftly(tor_config(default = "nz(123)"))] pub(super) nzu8_2: NonZero, #[deftly(tor_config(default))] pub(super) dur: Duration, #[deftly(tor_config(default))] pub(super) s: String, } fn nz(x: u8) -> NonZeroU8 { x.try_into().unwrap() } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct CfgEnabled { // MSRV 1.88: Use "true" instead. #[deftly(tor_config( default, cfg = "all()", cfg_desc = "with eschaton immenentization" ))] pub(super) flower_power: u32, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct CfgDisabled { // MSRV 1.88: Use "false" instead. #[deftly(tor_config( default, cfg = "any()", cfg_desc = "with resublimated thiotimoline" ))] pub(super) time_travel: u32, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config( pre_build = "Self::check_odd", post_build = "CfgValidating::check_even" ))] pub(super) struct CfgValidating { #[deftly(tor_config(default = "1"))] pub(super) odd: u32, #[deftly(tor_config(default))] pub(super) even: u32, } impl CfgValidatingBuilder { fn check_odd(&self) -> Result<(), crate::ConfigBuildError> { if let Some(v) = self.odd { if v & 1 != 1 { return Err(crate::ConfigBuildError::Invalid { field: "odd".to_string(), problem: "Not odd".to_string(), }); } } Ok(()) } } impl CfgValidating { fn check_even(self) -> Result { if self.even & 1 != 0 { return Err(crate::ConfigBuildError::Invalid { field: "even".to_string(), problem: "Not even".to_string(), }); } Ok(self) } } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config( no_serialize_trait, no_test_default, no_extendbuilder_trait, no_flattenable_trait ))] pub(super) struct CfgGeneric where T: Clone + std::fmt::Debug + PartialEq + Default, U: Clone + std::fmt::Debug + PartialEq + Default, { #[deftly(tor_config(default, no_magic))] pub(super) t: T, #[deftly(tor_config(default, no_magic))] pub(super) u: U, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct OptionsCfg { #[deftly(tor_config(default))] pub(super) a: Option, #[deftly(tor_config(default = "Some(123)"))] pub(super) b: Option, #[deftly(tor_config(default = "Some(nz(42))"))] pub(super) nz: Option, #[deftly(tor_config(default))] pub(super) s: Option, #[deftly(tor_config(default))] pub(super) other: Option<(u32, u32)>, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config(attr = "derive(Eq,Ord,PartialOrd,PartialEq)"))] pub(super) struct AttribsCfg { #[deftly(tor_config(default))] #[deftly(tor_config(attr = r#"serde(alias = "fun_with_numbers")"#))] #[deftly(tor_config(serde = r#"alias = "its_fun_to_count""#))] pub(super) a: u32, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct SettersCfg { #[deftly(tor_config(default, setter(skip)))] pub(super) a: u32, #[deftly(tor_config(default, setter(into)))] pub(super) b: u32, #[deftly(tor_config(default, setter(try_into)))] pub(super) c: u32, #[deftly(tor_config(default, setter(strip_option)))] pub(super) d: Option, #[deftly(tor_config(default, setter(name = "set_the_e")))] pub(super) e: u32, } impl SettersCfgBuilder { // Custom setter. pub(super) fn a(&mut self, val: u32) { self.a = Some(val * 2); } } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config(no_serialize_trait, no_deserialize_trait))] pub(super) struct NoSerdeCfg { #[deftly(tor_config(default))] pub(super) ip: u128, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] #[deftly(tor_config(build_fn(name = "build_this")))] pub(super) struct RenameBuild { #[deftly(tor_config(default))] pub(super) member: u16, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct RenameSubBuild { #[deftly(tor_config(sub_builder(build_fn = "build_this")))] pub(super) inner: RenameBuild, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct FullyCustom { #[deftly(tor_config( setter(skip), field(ty = "(u32, u32)", vis = "pub(super)"), build = r#"|this: &Self| format!("{} {}", this.value.0, this.value.1)"#, extend_with = r#"|mine: &mut (u32,u32), theirs: (u32,u32), _| *mine = theirs"#, ))] pub(super) value: String, } impl FullyCustomBuilder { pub(super) fn try_the_thing(&mut self, x: u64) { self.value = ((x >> 32) as u32, x as u32); } } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct FieldSkipCfg { #[deftly(tor_config(skip, build = r#"|_this: &Self| 25"#))] pub(super) value: u32, } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct ListsCfg { #[deftly(tor_config(list(element(clone)), default = "vec![7]"))] pub(super) integers: Vec, #[deftly(tor_config( list(listtype = "StringSet", element(clone)), default = "cats()" ))] pub(super) cats: BTreeSet, #[deftly(tor_config(list(element(build)), default = "vec![]"))] pub(super) simple: Vec, } fn cats() -> Vec { ["Damiano", "Moonbeam", "Checkers", "Enigma"] .iter() .map(|s| s.to_string()) .collect() } #[derive(Deftly, Clone, Debug, PartialEq)] #[derive_deftly(TorConfig)] pub(super) struct MapCfg { #[deftly(tor_config(map, default = "default_map()"))] pub(super) map: HashMap, } fn default_map() -> HashMap { let mut b = SimpleBuilder::new(); b.abc(32); b.xyz(123); let mut m = HashMap::new(); m.insert("pangolin".to_string(), b); m } } #[test] fn test_simple_defaults() { let b = t::SimpleBuilder::new(); let x = b.build().unwrap(); assert_eq!(x.xyz, 0); assert_eq!(x.abc, 3); assert_eq!(x.forty_two, 42); assert_eq!(x.forty_three, 43); } #[test] fn test_simple_setters() { let mut b = t::SimpleBuilder::new(); let x = b.abc(7).xyz(77).forty_two(777).build().unwrap(); assert_eq!(x.xyz, 77); assert_eq!(x.abc, 7); assert_eq!(x.forty_two, 42); assert_eq!(x.forty_three, 43); } #[test] fn test_simple_serde() { let v = r#" xyz = 7 "#; let b: t::SimpleBuilder = toml::from_str(v).unwrap(); let x = b.build().unwrap(); assert_eq!(x.xyz, 7); assert_eq!(x.abc, 3); assert_eq!(x.forty_two, 42); assert_eq!(x.forty_three, 43); } #[test] fn test_field_no_default() { let e = t::FieldNoDefaultBuilder::new().build().unwrap_err(); assert_matches!( e, ConfigBuildError::MissingField { field } if field == "has_no_default" ); let v = t::FieldNoDefaultBuilder::new() .has_no_default(42) .build() .unwrap(); assert_eq!(v.has_default, 0); assert_eq!(v.has_no_default, 42); } #[test] fn test_subbuilder() { let e = t::SubBuilder::new().build().unwrap_err(); assert_matches!( e, ConfigBuildError::MissingField { field } if field == "fnd.has_no_default" ); let mut b = t::SubBuilder::new(); b.fnd().has_no_default(5).has_default(66); b.simple().abc(123); b.s("Hello"); let v = b.build().unwrap(); assert_eq!(v.fnd.has_no_default, 5); assert_eq!(v.fnd.has_default, 66); assert_eq!(v.simple.abc, 123); assert_eq!(v.simple.xyz, 0); assert_eq!(v.s, "Hello"); } #[test] fn test_subbuilder_serde() { let v = r#" s = "hello world" [fnd] has_no_default = 1234 "#; let b: t::SubBuilder = toml::from_str(v).unwrap(); let x = b.build().unwrap(); assert_eq!(x.fnd.has_no_default, 1234); assert_eq!(x.fnd.has_default, 0); assert_eq!(x.s, "hello world"); } #[test] fn test_magic_nz() { let mut b = t::Magic::builder(); b.nzu8(123); b.nzu8_2(1); let v = b.build().unwrap(); assert_eq!(v.nzu8.get(), 123); assert_eq!(v.nzu8_2.get(), 1); let e = t::MagicBuilder::new().nzu8(0).build().unwrap_err(); let ConfigBuildError::Invalid { field, problem } = e else { panic!("Error not as expected ({e:?})"); }; assert_eq!(field, "nzu8"); assert_eq!(problem, "value not allowed to be zero"); let e = t::MagicBuilder::new().nzu8_2(0).build().unwrap_err(); let ConfigBuildError::Invalid { field, problem } = e else { panic!("Error not as expected ({e:?})"); }; assert_eq!(field, "nzu8_2"); assert_eq!(problem, "value not allowed to be zero"); } #[test] fn test_magic_string() { let mut b = t::Magic::builder(); b.s("hello"); // <-- note that this is not a String. let v = b.build().unwrap(); assert_eq!(v.s, "hello"); #[allow(clippy::unnecessary_to_owned)] b.s("world".to_string()); let v = b.build().unwrap(); assert_eq!(v.s, "world"); } #[test] fn test_magic_duration() { let v = r#" dur = "1 hour" "#; let b: t::MagicBuilder = toml::from_str(v).unwrap(); let x = b.build().unwrap(); assert_eq!(x.dur, std::time::Duration::new(60 * 60, 0)); } #[test] #[traced_test] fn test_cfg_enabled() { let s = r#" flower_power = 12 "#; let b: t::CfgEnabledBuilder = toml::from_str(s).unwrap(); let v = b.build().unwrap(); assert_eq!(v.flower_power, 12); assert!(!logs_contain("no effect")); } #[test] #[traced_test] fn test_cfg_disabled() { let s = r#" time_travel = "hello world" "#; let b: t::CfgDisabledBuilder = toml::from_str(s).unwrap(); let v = b.build().unwrap(); assert_eq!(v.time_travel, 0); assert!(logs_contain( "Ignored configuration for 'time_travel'. \ This option has no effect unless the program is built with resublimated thiotimoline" )); } #[test] fn test_validating() { let err_notodd = t::CfgValidating::builder().odd(6).build().unwrap_err(); assert_eq!( err_notodd.to_string(), "Value of odd was incorrect: Not odd" ); let err_noteven = t::CfgValidating::builder().even(5).build().unwrap_err(); assert_eq!( err_noteven.to_string(), "Value of even was incorrect: Not even" ); let v = t::CfgValidating::builder().odd(5).even(6).build().unwrap(); assert_eq!(v.odd, 5); assert_eq!(v.even, 6); } #[test] fn test_generic() { let mut b = t::CfgGeneric::>::builder(); b.t("This is a test".to_string()); b.u(vec![1, 2, 3]); let v = b.build().unwrap(); assert_eq!(v.t, "This is a test"); assert_eq!(&v.u, &[1, 2, 3]); } #[test] fn test_options_setters() { let mut b = t::OptionsCfg::builder(); // Try with no-option inputs. b.a(32); b.s("hello"); b.other((1, 2)); b.nz(12); let c = b.build().unwrap(); assert_eq!(c.a, Some(32)); assert_eq!(c.b, Some(123)); assert_eq!(c.s, Some("hello".to_string())); assert_eq!(c.other, Some((1, 2))); assert_eq!(c.nz, Some(12.try_into().unwrap())); // Try with option inputs. b.a(Some(12)); b.b(None); b.s(Some("world")); b.other(Some((11, 22))); b.nz(Some(std::num::NonZeroU8::new(15).unwrap())); let c = b.build().unwrap(); assert_eq!(c.a, Some(12)); assert_eq!(c.b, None); assert_eq!(c.s, Some("world".to_string())); assert_eq!(c.other, Some((11, 22))); assert_eq!(c.nz, Some(15.try_into().unwrap())); } #[test] fn test_attributes() { let s1 = "fun_with_numbers = 1982374"; let s2 = "its_fun_to_count = 1982375"; let b1: t::AttribsCfgBuilder = toml::from_str(s1).unwrap(); let b2: t::AttribsCfgBuilder = toml::from_str(s2).unwrap(); // Make sure that derive(PartialEq, Ord) happened. assert_eq!(b1, b1); assert_ne!(b1, b2); assert!(b1 < b2); } #[test] fn test_setter_meta() { let mut b = t::SettersCfgBuilder::new(); b.a(7); b.b(5_u8); assert!(b.c(1_u64 << 40).is_err()); b.c(100_u64).unwrap(); b.d(19); b.set_the_e(22); let v = b.build().unwrap(); assert_eq!(v.a, 7 * 2); assert_eq!(v.b, 5_u32); assert_eq!(v.c, 100_u32); assert_eq!(v.d, Some(19)); assert_eq!(v.e, 22); } #[test] fn test_build_fn_rename() { let mut b = t::RenameBuild::builder(); let v = b.member(6).build_this().unwrap(); assert_eq!(v.member, 6); let mut b = t::RenameSubBuild::builder(); b.inner().member(5); let v = b.build().unwrap(); assert_eq!(v.inner.member, 5); } #[test] fn test_custom() { let mut b = t::FullyCustomBuilder::new(); b.try_the_thing(0xF00B00B512345678); assert_eq!(b.value, (0xF00B00B5, 0x12345678)); let v = b.build().unwrap(); assert_eq!(v.value, "4027252917 305419896"); } #[test] fn test_field_skip() { let c = t::FieldSkipCfg::builder().build().unwrap(); assert_eq!(c.value, 25); } #[test] fn test_list_builder() { let mut b = t::ListsCfgBuilder::new(); b.set_integers(vec![12, 6, 3]); let c = b.build().unwrap(); assert_eq!(&c.integers[..], &[12, 6, 3]); assert!(c.cats.contains("Moonbeam")); assert!(c.cats.contains("Damiano")); assert!(c.simple.is_empty()); let b = t::ListsCfgBuilder::new(); let c = b.build().unwrap(); assert_eq!(&c.integers[..], &[7]); let mut b = t::ListsCfgBuilder::new(); b.integers().push(22); b.cats().remove(0); b.cats().push("Frida".to_string()); b.simple().push(t::SimpleBuilder::new()); let c = b.build().unwrap(); assert_eq!(&c.integers[..], &[7, 22]); assert!(c.cats.contains("Frida")); assert!(!c.cats.contains("Damiano")); assert_eq!(c.simple.len(), 1); } #[test] fn test_list_builder_serde() { let s1 = r#" integers = [ 1,2,3 ] "#; let s2 = r#" cats = [ "Prof. Jiggly", "Jorts" ] "#; let s3 = r#" cats = [ "Jenny" ] [[simple]] xyz = 9 abc = 12 [[simple]] xyz = 16 "#; let b1: t::ListsCfgBuilder = toml::from_str(s1).unwrap(); let b2: t::ListsCfgBuilder = toml::from_str(s2).unwrap(); let b3: t::ListsCfgBuilder = toml::from_str(s3).unwrap(); let c1 = b1.build().unwrap(); let c2 = b2.build().unwrap(); let c3 = b3.build().unwrap(); assert_eq!(&c1.integers[..], &[1, 2, 3]); assert!(c1.cats.contains("Checkers")); assert!(!c1.cats.contains("Jorts")); assert_eq!(&c2.integers[..], &[7]); assert_eq!(c2.cats.len(), 2); assert!(c2.cats.contains("Jorts")); assert!(!c2.cats.contains("Checkers")); assert_eq!(c3.cats.len(), 1); assert_eq!(c3.simple.len(), 2); assert_eq!(c3.simple[0].xyz, 9); assert_eq!(c3.simple[0].abc, 12); assert_eq!(c3.simple[1].xyz, 16); assert_eq!(c3.simple[1].abc, 3); } #[test] fn test_map_builder() { let mut b = t::MapCfg::builder(); { let mut sb = t::Simple::builder(); sb.xyz(11); b.map().insert("Hello".to_string(), sb); } { let mut sb = t::Simple::builder(); sb.abc(33); b.map().insert("World".to_string(), sb); } let c = b.build().unwrap(); assert_eq!(c.map.len(), 3); assert_eq!(c.map.get("Hello").unwrap().xyz, 11); assert_eq!(c.map.get("Hello").unwrap().abc, 3); assert_eq!(c.map.get("World").unwrap().xyz, 0); assert_eq!(c.map.get("World").unwrap().abc, 33); assert_eq!(c.map.get("pangolin").unwrap().xyz, 123); assert_eq!(c.map.get("pangolin").unwrap().abc, 32); } }