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; )?
} => { ... };
{@if_empty {} {$($x:tt)*}} => { ... };
{@if_empty {$($y:tt)*} {$($x:tt)*}} => { ... };
}
Expand description
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
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
andValueType
may be replaced with any appropriate types.ValueType
must have a correspondingValueTypeBuilder
.ValueTypeBuilder
must implementExtendBuilder
.
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
andDerefMut
with a target type ofContainer<KeyType, ValueTypeBuilder>
Default
,Clone
, andDebug
.Serialize
andDeserialize
- A
build()
function that invokesbuild()
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
#[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
}
define_map_builder! {
pub struct ConnectionMapBuilder =>
pub type ConnectionMap = BTreeMap<String, ConnConfig>;
}
#[derive(Clone, Debug, Builder, Deftly, Eq, PartialEq)]
#[derive_deftly(ExtendBuilder)]
#[builder(build_fn(error = "ConfigBuildError"))]
#[builder(derive(Debug, Serialize, Deserialize))]
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."http"]
enabled = false
[conns."quokka"]
enabled = true
port = 9999
"#).unwrap();
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:
pub type ConnectionMap = BTreeMap<String, ConnConfig>;
#[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.