#[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()
}
}
$(
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> {
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)? } {
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> {
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 {
#![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)]
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)]
#[builder(derive(Deserialize, Serialize, Debug))]
#[builder(build_fn(error = "ConfigBuildError"))]
struct Inner {
#[builder(default)]
fun: bool,
#[builder(default)]
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
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);
}
}