Lines
100 %
Functions
56.34 %
Branches
//! Helper for defining sub-builders that map a serializable type (typically String)
//! to a configuration type.
/// Define a map type, and an associated builder struct, suitable for use in a configuration object.
///
/// We use this macro when we want a configuration structure to contain a key-to-value map,
/// and therefore we want its associated builder structure to contain
/// a map from the same key type to a value-builder type.
/// The key of the map type must implement `Serialize`, `Clone`, and `Debug`.
/// The value of the map type must have an associated "Builder"
/// type formed by appending `Builder` to its name.
/// This Builder type must implement `Serialize`, `Deserialize`, `Clone`, and `Debug`,
/// and it must have a `build(&self)` method returning `Result<value, ConfigBuildError>`.
/// # Syntax and behavior
/// ```ignore
/// define_map_builder! {
/// BuilderAttributes
/// pub struct BuilderName =>
/// MapAttributes
/// pub type MapName = ContainerType<KeyType, ValueType>;
/// defaults: defaults_func(); // <--- this line is optional
/// }
/// ```
/// In the example above,
/// * BuilderName, MapName, and ContainerType may be replaced with any identifier;
/// * BuilderAttributes and MapAttributes can be replaced with any set of attributes
/// (such sa doc comments, `#derive`, and so on);
/// * The `pub`s may be replaced with any visibility;
/// * and `KeyType` and `ValueType` may be replaced with any appropriate types.
/// * `ValueType` must have a corresponding `ValueTypeBuilder`.
/// * `ValueTypeBuilder` must implement
/// [`ExtendBuilder`](crate::extend_builder::ExtendBuilder).
/// Given this syntax, this macro will define "MapType" as an alias for
/// `Container<KeyType,ValueType>`,
/// and "BuilderName" as a builder type for that container.
/// "BuilderName" will implement:
/// * `Deref` and `DerefMut` with a target type of `Container<KeyType, ValueTypeBuilder>`
/// * `Default`, `Clone`, and `Debug`.
/// * `Serialize` and `Deserialize`
/// * A `build()` function that invokes `build()` on every value in its contained map.
/// (Note that in order to work as a sub-builder within our configuration system,
/// "BuilderName" should be the same as "MapName" concatenated with "Builder.")
/// The `defaults_func()`, if provided, must be
/// a function returning `ContainerType<KeyType, ValueType>`.
/// The values returned by `default_func()` map are used to implement
/// `Default` and `Deserialize` for `BuilderName`,
/// extending from the defaults with `ExtendStrategy::ReplaceLists`.
/// If no `defaults_func` is given, `ContainerType::default()` is used.
/// # Example
/// # use derive_builder::Builder;
/// # use derive_deftly::Deftly;
/// # use std::collections::BTreeMap;
/// # use tor_config::{ConfigBuildError, define_map_builder, derive_deftly_template_ExtendBuilder};
/// # use serde::{Serialize, Deserialize};
/// # use tor_config::extend_builder::{ExtendBuilder,ExtendStrategy};
/// #[derive(Clone, Debug, Builder, Deftly, Eq, PartialEq)]
/// #[derive_deftly(ExtendBuilder)]
/// #[builder(build_fn(error = "ConfigBuildError"))]
/// #[builder(derive(Debug, Serialize, Deserialize))]
/// pub struct ConnectionsConfig {
/// #[builder(sub_builder)]
/// #[deftly(extend_builder(sub_builder))]
/// conns: ConnectionMap
/// pub struct ConnectionMapBuilder =>
/// pub type ConnectionMap = BTreeMap<String, ConnConfig>;
/// pub struct ConnConfig {
/// #[builder(default="true")]
/// enabled: bool,
/// port: u16,
/// let defaults: ConnectionsConfigBuilder = toml::from_str(r#"
/// [conns."socks"]
/// enabled = true
/// port = 9150
/// [conns."http"]
/// enabled = false
/// port = 1234
/// [conns."wombat"]
/// port = 5050
/// "#).unwrap();
/// let user_settings: ConnectionsConfigBuilder = toml::from_str(r#"
/// [conns."quokka"]
/// port = 9999
/// let mut cfg = defaults.clone();
/// cfg.extend_from(user_settings, ExtendStrategy::ReplaceLists);
/// let cfg = cfg.build().unwrap();
/// assert_eq!(cfg, ConnectionsConfig {
/// conns: vec![
/// ("http".into(), ConnConfig { enabled: false, port: 1234}),
/// ("quokka".into(), ConnConfig { enabled: true, port: 9999}),
/// ("socks".into(), ConnConfig { enabled: true, port: 9150}),
/// ("wombat".into(), ConnConfig { enabled: true, port: 5050}),
/// ].into_iter().collect(),
/// });
/// In the example above, the `derive_map_builder` macro expands to something like:
/// #[derive(Clone,Debug,Serialize,Educe)]
/// #[educe(Deref,DerefMut)]
/// pub struct ConnectionMapBuilder(BTreeMap<String, ConnConfigBuilder);
/// impl ConnectionMapBuilder {
/// fn build(&self) -> Result<ConnectionMap, ConfigBuildError> {
/// ...
/// impl Default for ConnectionMapBuilder { ... }
/// impl Deserialize for ConnectionMapBuilder { ... }
/// impl ExtendBuilder for ConnectionMapBuilder { ... }
/// # Notes and rationale
/// We use this macro, instead of using a Map directly in our configuration object,
/// so that we can have a separate Builder type with a reasonable build() implementation.
/// We don't support complicated keys; instead we require that the keys implement Deserialize.
/// If we ever need to support keys with their own builders,
/// we'll have to define a new macro.
/// We use `ExtendBuilder` to implement Deserialize with defaults,
/// so that the provided configuration options can override
/// only those parts of the default configuration tree
/// that they actually replace.
#[macro_export]
macro_rules! define_map_builder {
{
$(#[ $b_m:meta ])*
$b_v:vis struct $btype:ident =>
$(#[ $m:meta ])*
$v:vis type $maptype:ident = $coltype:ident < $keytype:ty , $valtype: ty >;
$( defaults: $defaults:expr; )?
} =>
{paste::paste!{
$(#[ $m ])*
$v type $maptype = $coltype < $keytype , $valtype > ;
$(#[ $b_m ])*
#[derive(Clone,Debug,$crate::deps::serde::Serialize, $crate::deps::educe::Educe)]
#[educe(Deref, DerefMut)]
#[serde(transparent)]
$b_v struct $btype( $coltype < $keytype, [<$valtype Builder>] > );
impl $btype {
$b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
self.0
.iter()
.map(|(k,v)| Ok((k.clone(), v.build()?)))
.collect()
}
$(
// This section is expanded when we have a defaults_fn().
impl ::std::default::Default for $btype {
fn default() -> Self {
Self($defaults)
impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: $crate::deps::serde::Deserializer<'de> {
// To deserialize into this type, we create a builder holding the defaults,
// and we create a builder holding the values from the deserializer.
// We then use ExtendBuilder to extend the defaults with the deserialized values.
let deserialized = $coltype::<$keytype, [<$valtype Builder>]>::deserialize(deserializer)?;
let mut defaults = $btype::default();
$crate::extend_builder::ExtendBuilder::extend_from(
&mut defaults,
Self(deserialized),
$crate::extend_builder::ExtendStrategy::ReplaceLists);
Ok(defaults)
)?
$crate::define_map_builder!{@if_empty { $($defaults)? } {
// This section is expanded when we don't have a defaults_fn().
Self(Default::default())
// We can't conditionally derive() here, since Rust doesn't like macros that expand to
// attributes.
Ok(Self($coltype::deserialize(deserializer)?))
}}
impl $crate::extend_builder::ExtendBuilder for $btype
fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
$crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
}};
{@if_empty {} {$($x:tt)*}} => {$($x)*};
{@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
#[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_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use crate::ConfigBuildError;
use derive_builder::Builder;
use derive_deftly::Deftly;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Debug, Eq, PartialEq, Builder)]
#[builder(derive(Deserialize, Serialize, Debug))]
#[builder(build_fn(error = "ConfigBuildError"))]
struct Outer {
#[builder(sub_builder(fn_name = "build"))]
#[builder_field_attr(serde(default))]
things: ThingMap,
#[derive(Clone, Debug, Eq, PartialEq, Builder, Deftly)]
#[derive_deftly(ExtendBuilder)]
struct Inner {
#[builder(default)]
fun: bool,
explosive: bool,
define_map_builder! {
struct ThingMapBuilder =>
type ThingMap = BTreeMap<String, Inner>;
#[test]
fn parse_and_build() {
let builder: OuterBuilder = toml::from_str(
r#"
[things.x]
fun = true
explosive = false
[things.yy]
explosive = true
"#,
)
.unwrap();
let built = builder.build().unwrap();
assert_eq!(
built.things.get("x").unwrap(),
&Inner {
fun: true,
explosive: false
);
built.things.get("yy").unwrap(),
explosive: true
fn build_directly() {
let mut builder = OuterBuilder::default();
let mut bld = InnerBuilder::default();
bld.fun(true);
builder.things().insert("x".into(), bld);
struct ThingMap2Builder =>
type ThingMap2 = BTreeMap<String, Inner>;
defaults: thingmap2_default();
fn thingmap2_default() -> BTreeMap<String, InnerBuilder> {
let mut map = BTreeMap::new();
map.insert("x".to_string(), bld);
bld.explosive(true);
map.insert("y".to_string(), bld);
map
fn with_defaults() {
let mut tm2 = ThingMap2Builder::default();
tm2.get_mut("x").unwrap().explosive(true);
tm2.insert("zz".into(), bld);
let built = tm2.build().unwrap();
built.get("x").unwrap(),
built.get("y").unwrap(),
fun: false,
built.get("zz").unwrap(),
let tm2: ThingMap2Builder = toml::from_str(
[x]
[zz]
let built2 = tm2.build().unwrap();
assert_eq!(built, built2);