#[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; )?
} => {
$crate::deps::paste!{$crate::define_map_builder! {
$(#[$b_m])*
$b_v struct $btype =>
$(#[$m])*
$v type $maptype = {
map: $coltype < $keytype , $valtype >,
builder_map: $coltype < $keytype, [<$valtype Builder>] > ,
}
$( defaults: $defaults; )?
}}
};
{
$(#[ $b_m:meta ])*
$b_v:vis struct $btype:ident =>
$(#[ $m:meta ])*
$v:vis type $maptype:ident = {
map: $mtype:ty,
builder_map: $bmtype:ty $(,)?
}
$( defaults: $defaults:expr; )?
} =>
{$crate::deps::paste!{
$(#[ $m ])*
$v type $maptype = $mtype ;
$(#[ $b_m ])*
#[derive(Clone,Debug,$crate::deps::serde::Serialize, $crate::deps::educe::Educe)]
#[educe(Deref, DerefMut)]
#[serde(transparent)]
$b_v struct $btype( $bmtype );
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()
}
}
impl $crate::load::Builder for $btype {
type Built = $maptype;
fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
$btype :: build(self)
}
}
impl $crate::load::ConfigBuilder for $btype {
fn apply_defaults(&mut self) -> ::std::result::Result<(), $crate::ConfigBuildError> {
#[allow(unused_imports)]
use $crate::load::ConfigBuilder as _;
for v in self.0.values_mut() {
v.apply_defaults()?;
}
Ok(())
}
}
$(
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> {
use $crate::deps::serde::Deserialize;
let deserialized = <$bmtype as Deserialize>::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)? } {
impl ::std::default::Default for $btype {
fn default() -> Self {
Self(Default::default())
}
}
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> {
use $crate::deps::serde::Deserialize;
Ok(Self(<$bmtype as Deserialize>::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 {
#![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::derive::prelude::*;
use derive_deftly::Deftly;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Eq, PartialEq, Deftly)]
#[derive_deftly(TorConfig)]
struct Outer {
#[deftly(tor_config(sub_builder, no_magic))]
things: ThingMap,
}
#[derive(Clone, Debug, Eq, PartialEq, Deftly)]
#[derive_deftly(TorConfig)]
struct Inner {
#[deftly(tor_config(default))]
fun: bool,
#[deftly(tor_config(default))]
explosive: bool,
}
impl InnerBuilder {
#[expect(clippy::unnecessary_wraps)]
fn apply_defaults(&mut self) -> Result<(), crate::ConfigBuildError> {
self.fun.get_or_insert_default();
self.explosive.get_or_insert_default();
Ok(())
}
}
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
fun = true
"#,
)
.unwrap();
let built = builder.build().unwrap();
assert_eq!(
built.things.get("x").unwrap(),
&Inner {
fun: true,
explosive: false
}
);
assert_eq!(
built.things.get("yy").unwrap(),
&Inner {
fun: true,
explosive: true
}
);
}
#[test]
fn build_directly() {
let mut builder = OuterBuilder::default();
let mut bld = InnerBuilder::default();
bld.fun(true);
builder.things().insert("x".into(), bld);
let built = builder.build().unwrap();
assert_eq!(
built.things.get("x").unwrap(),
&Inner {
fun: true,
explosive: false
}
);
}
define_map_builder! {
struct ThingMap2Builder =>
type ThingMap2 = BTreeMap<String, Inner>;
defaults: thingmap2_default();
}
fn thingmap2_default() -> BTreeMap<String, InnerBuilder> {
let mut map = BTreeMap::new();
{
let mut bld = InnerBuilder::default();
bld.fun(true);
map.insert("x".to_string(), bld);
}
{
let mut bld = InnerBuilder::default();
bld.explosive(true);
map.insert("y".to_string(), bld);
}
map
}
#[test]
fn with_defaults() {
let mut tm2 = ThingMap2Builder::default();
tm2.get_mut("x").unwrap().explosive(true);
let mut bld = InnerBuilder::default();
bld.fun(true);
tm2.insert("zz".into(), bld);
let built = tm2.build().unwrap();
assert_eq!(
built.get("x").unwrap(),
&Inner {
fun: true,
explosive: true
}
);
assert_eq!(
built.get("y").unwrap(),
&Inner {
fun: false,
explosive: true
}
);
assert_eq!(
built.get("zz").unwrap(),
&Inner {
fun: true,
explosive: false
}
);
let tm2: ThingMap2Builder = toml::from_str(
r#"
[x]
explosive = true
[zz]
fun = true
"#,
)
.unwrap();
let built2 = tm2.build().unwrap();
assert_eq!(built, built2);
}
}