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
pubs may be replaced with any visibility; - and
KeyTypeandValueTypemay be replaced with any appropriate types.ValueTypemust have a correspondingValueTypeBuilder.ValueTypeBuildermust 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:
DerefandDerefMutwith a target type ofContainer<KeyType, ValueTypeBuilder>Default,Clone, andDebug.SerializeandDeserialize- 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.