tor_config/map_builder.rs
1//! Helper for defining sub-builders that map a serializable type (typically String)
2//! to a configuration type.
3
4/// Define a map type, and an associated builder struct, suitable for use in a configuration object.
5///
6/// We use this macro when we want a configuration structure to contain a key-to-value map,
7/// and therefore we want its associated builder structure to contain
8/// a map from the same key type to a value-builder type.
9///
10/// The key of the map type must implement `Serialize`, `Clone`, and `Debug`.
11/// The value of the map type must have an associated "Builder"
12/// type formed by appending `Builder` to its name.
13/// This Builder type must implement `Serialize`, `Deserialize`, `Clone`, and `Debug`,
14/// and it must have a `build(&self)` method returning `Result<value, ConfigBuildError>`.
15///
16/// # Syntax and behavior
17///
18/// ```ignore
19/// define_map_builder! {
20/// BuilderAttributes
21/// pub struct BuilderName =>
22///
23/// MapAttributes
24/// pub type MapName = ContainerType<KeyType, ValueType>;
25///
26/// defaults: defaults_func(); // <--- this line is optional
27/// }
28/// ```
29///
30/// In the example above,
31///
32/// * BuilderName, MapName, and ContainerType may be replaced with any identifier;
33/// * BuilderAttributes and MapAttributes can be replaced with any set of attributes
34/// (such sa doc comments, `#derive`, and so on);
35/// * The `pub`s may be replaced with any visibility;
36/// * and `KeyType` and `ValueType` may be replaced with any appropriate types.
37/// * `ValueType` must have a corresponding `ValueTypeBuilder`.
38/// * `ValueTypeBuilder` must implement
39/// [`ExtendBuilder`](crate::extend_builder::ExtendBuilder).
40///
41/// Given this syntax, this macro will define "MapType" as an alias for
42/// `Container<KeyType,ValueType>`,
43/// and "BuilderName" as a builder type for that container.
44///
45/// "BuilderName" will implement:
46/// * `Deref` and `DerefMut` with a target type of `Container<KeyType, ValueTypeBuilder>`
47/// * `Default`, `Clone`, and `Debug`.
48/// * `Serialize` and `Deserialize`
49/// * A `build()` function that invokes `build()` on every value in its contained map.
50///
51/// (Note that in order to work as a sub-builder within our configuration system,
52/// "BuilderName" should be the same as "MapName" concatenated with "Builder.")
53///
54/// The `defaults_func()`, if provided, must be
55/// a function returning `ContainerType<KeyType, ValueType>`.
56/// The values returned by `default_func()` map are used to implement
57/// `Default` and `Deserialize` for `BuilderName`,
58/// extending from the defaults with `ExtendStrategy::ReplaceLists`.
59/// If no `defaults_func` is given, `ContainerType::default()` is used.
60///
61/// # Example
62///
63/// ```
64/// # use derive_builder::Builder;
65/// # use derive_deftly::Deftly;
66/// # use std::collections::BTreeMap;
67/// # use tor_config::{ConfigBuildError, define_map_builder, derive_deftly_template_ExtendBuilder};
68/// # use serde::{Serialize, Deserialize};
69/// # use tor_config::extend_builder::{ExtendBuilder,ExtendStrategy};
70/// #[derive(Clone, Debug, Builder, Deftly, Eq, PartialEq)]
71/// #[derive_deftly(ExtendBuilder)]
72/// #[builder(build_fn(error = "ConfigBuildError"))]
73/// #[builder(derive(Debug, Serialize, Deserialize))]
74/// pub struct ConnectionsConfig {
75/// #[builder(sub_builder)]
76/// #[deftly(extend_builder(sub_builder))]
77/// conns: ConnectionMap
78/// }
79///
80/// define_map_builder! {
81/// pub struct ConnectionMapBuilder =>
82/// pub type ConnectionMap = BTreeMap<String, ConnConfig>;
83/// }
84///
85/// #[derive(Clone, Debug, Builder, Deftly, Eq, PartialEq)]
86/// #[derive_deftly(ExtendBuilder)]
87/// #[builder(build_fn(error = "ConfigBuildError"))]
88/// #[builder(derive(Debug, Serialize, Deserialize))]
89/// pub struct ConnConfig {
90/// #[builder(default="true")]
91/// enabled: bool,
92/// port: u16,
93/// }
94///
95/// let defaults: ConnectionsConfigBuilder = toml::from_str(r#"
96/// [conns."socks"]
97/// enabled = true
98/// port = 9150
99///
100/// [conns."http"]
101/// enabled = false
102/// port = 1234
103///
104/// [conns."wombat"]
105/// port = 5050
106/// "#).unwrap();
107/// let user_settings: ConnectionsConfigBuilder = toml::from_str(r#"
108/// [conns."http"]
109/// enabled = false
110/// [conns."quokka"]
111/// enabled = true
112/// port = 9999
113/// "#).unwrap();
114///
115/// let mut cfg = defaults.clone();
116/// cfg.extend_from(user_settings, ExtendStrategy::ReplaceLists);
117/// let cfg = cfg.build().unwrap();
118/// assert_eq!(cfg, ConnectionsConfig {
119/// conns: vec![
120/// ("http".into(), ConnConfig { enabled: false, port: 1234}),
121/// ("quokka".into(), ConnConfig { enabled: true, port: 9999}),
122/// ("socks".into(), ConnConfig { enabled: true, port: 9150}),
123/// ("wombat".into(), ConnConfig { enabled: true, port: 5050}),
124/// ].into_iter().collect(),
125/// });
126/// ```
127///
128/// In the example above, the `derive_map_builder` macro expands to something like:
129///
130/// ```ignore
131/// pub type ConnectionMap = BTreeMap<String, ConnConfig>;
132///
133/// #[derive(Clone,Debug,Serialize,Educe)]
134/// #[educe(Deref,DerefMut)]
135/// pub struct ConnectionMapBuilder(BTreeMap<String, ConnConfigBuilder);
136///
137/// impl ConnectionMapBuilder {
138/// fn build(&self) -> Result<ConnectionMap, ConfigBuildError> {
139/// ...
140/// }
141/// }
142/// impl Default for ConnectionMapBuilder { ... }
143/// impl Deserialize for ConnectionMapBuilder { ... }
144/// impl ExtendBuilder for ConnectionMapBuilder { ... }
145/// ```
146///
147/// # Notes and rationale
148///
149/// We use this macro, instead of using a Map directly in our configuration object,
150/// so that we can have a separate Builder type with a reasonable build() implementation.
151///
152/// We don't support complicated keys; instead we require that the keys implement Deserialize.
153/// If we ever need to support keys with their own builders,
154/// we'll have to define a new macro.
155///
156/// We use `ExtendBuilder` to implement Deserialize with defaults,
157/// so that the provided configuration options can override
158/// only those parts of the default configuration tree
159/// that they actually replace.
160#[macro_export]
161macro_rules! define_map_builder {
162 {
163 $(#[ $b_m:meta ])*
164 $b_v:vis struct $btype:ident =>
165 $(#[ $m:meta ])*
166 $v:vis type $maptype:ident = $coltype:ident < $keytype:ty , $valtype: ty >;
167 $( defaults: $defaults:expr; )?
168 } =>
169 {paste::paste!{
170 $(#[ $m ])*
171 $v type $maptype = $coltype < $keytype , $valtype > ;
172
173 $(#[ $b_m ])*
174 #[derive(Clone,Debug,$crate::deps::serde::Serialize, $crate::deps::educe::Educe)]
175 #[educe(Deref, DerefMut)]
176 #[serde(transparent)]
177 $b_v struct $btype( $coltype < $keytype, [<$valtype Builder>] > );
178
179 impl $btype {
180 $b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
181 self.0
182 .iter()
183 .map(|(k,v)| Ok((k.clone(), v.build()?)))
184 .collect()
185 }
186 }
187 $(
188 // This section is expanded when we have a defaults_fn().
189 impl ::std::default::Default for $btype {
190 fn default() -> Self {
191 Self($defaults)
192 }
193 }
194 impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
195 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196 where
197 D: $crate::deps::serde::Deserializer<'de> {
198 // To deserialize into this type, we create a builder holding the defaults,
199 // and we create a builder holding the values from the deserializer.
200 // We then use ExtendBuilder to extend the defaults with the deserialized values.
201 let deserialized = $coltype::<$keytype, [<$valtype Builder>]>::deserialize(deserializer)?;
202 let mut defaults = $btype::default();
203 $crate::extend_builder::ExtendBuilder::extend_from(
204 &mut defaults,
205 Self(deserialized),
206 $crate::extend_builder::ExtendStrategy::ReplaceLists);
207 Ok(defaults)
208 }
209 }
210 )?
211 $crate::define_map_builder!{@if_empty { $($defaults)? } {
212 // This section is expanded when we don't have a defaults_fn().
213 impl ::std::default::Default for $btype {
214 fn default() -> Self {
215 Self(Default::default())
216 }
217 }
218 // We can't conditionally derive() here, since Rust doesn't like macros that expand to
219 // attributes.
220 impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: $crate::deps::serde::Deserializer<'de> {
224 Ok(Self($coltype::deserialize(deserializer)?))
225 }
226 }
227 }}
228 impl $crate::extend_builder::ExtendBuilder for $btype
229 {
230 fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
231 $crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
232 }
233 }
234 }};
235 {@if_empty {} {$($x:tt)*}} => {$($x)*};
236 {@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
237}
238
239#[cfg(test)]
240mod test {
241 // @@ begin test lint list maintained by maint/add_warning @@
242 #![allow(clippy::bool_assert_comparison)]
243 #![allow(clippy::clone_on_copy)]
244 #![allow(clippy::dbg_macro)]
245 #![allow(clippy::mixed_attributes_style)]
246 #![allow(clippy::print_stderr)]
247 #![allow(clippy::print_stdout)]
248 #![allow(clippy::single_char_pattern)]
249 #![allow(clippy::unwrap_used)]
250 #![allow(clippy::unchecked_duration_subtraction)]
251 #![allow(clippy::useless_vec)]
252 #![allow(clippy::needless_pass_by_value)]
253 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
254
255 use crate::ConfigBuildError;
256 use derive_builder::Builder;
257 use derive_deftly::Deftly;
258 use serde::{Deserialize, Serialize};
259 use std::collections::BTreeMap;
260
261 #[derive(Clone, Debug, Eq, PartialEq, Builder)]
262 #[builder(derive(Deserialize, Serialize, Debug))]
263 #[builder(build_fn(error = "ConfigBuildError"))]
264 struct Outer {
265 #[builder(sub_builder(fn_name = "build"))]
266 #[builder_field_attr(serde(default))]
267 things: ThingMap,
268 }
269
270 #[derive(Clone, Debug, Eq, PartialEq, Builder, Deftly)]
271 #[derive_deftly(ExtendBuilder)]
272 #[builder(derive(Deserialize, Serialize, Debug))]
273 #[builder(build_fn(error = "ConfigBuildError"))]
274 struct Inner {
275 #[builder(default)]
276 fun: bool,
277 #[builder(default)]
278 explosive: bool,
279 }
280
281 define_map_builder! {
282 struct ThingMapBuilder =>
283 type ThingMap = BTreeMap<String, Inner>;
284 }
285
286 #[test]
287 fn parse_and_build() {
288 let builder: OuterBuilder = toml::from_str(
289 r#"
290[things.x]
291fun = true
292explosive = false
293
294[things.yy]
295explosive = true
296fun = true
297"#,
298 )
299 .unwrap();
300
301 let built = builder.build().unwrap();
302 assert_eq!(
303 built.things.get("x").unwrap(),
304 &Inner {
305 fun: true,
306 explosive: false
307 }
308 );
309 assert_eq!(
310 built.things.get("yy").unwrap(),
311 &Inner {
312 fun: true,
313 explosive: true
314 }
315 );
316 }
317
318 #[test]
319 fn build_directly() {
320 let mut builder = OuterBuilder::default();
321 let mut bld = InnerBuilder::default();
322 bld.fun(true);
323 builder.things().insert("x".into(), bld);
324 let built = builder.build().unwrap();
325 assert_eq!(
326 built.things.get("x").unwrap(),
327 &Inner {
328 fun: true,
329 explosive: false
330 }
331 );
332 }
333
334 define_map_builder! {
335 struct ThingMap2Builder =>
336 type ThingMap2 = BTreeMap<String, Inner>;
337 defaults: thingmap2_default();
338 }
339 fn thingmap2_default() -> BTreeMap<String, InnerBuilder> {
340 let mut map = BTreeMap::new();
341 {
342 let mut bld = InnerBuilder::default();
343 bld.fun(true);
344 map.insert("x".to_string(), bld);
345 }
346 {
347 let mut bld = InnerBuilder::default();
348 bld.explosive(true);
349 map.insert("y".to_string(), bld);
350 }
351 map
352 }
353 #[test]
354 fn with_defaults() {
355 let mut tm2 = ThingMap2Builder::default();
356 tm2.get_mut("x").unwrap().explosive(true);
357 let mut bld = InnerBuilder::default();
358 bld.fun(true);
359 tm2.insert("zz".into(), bld);
360 let built = tm2.build().unwrap();
361
362 assert_eq!(
363 built.get("x").unwrap(),
364 &Inner {
365 fun: true,
366 explosive: true
367 }
368 );
369 assert_eq!(
370 built.get("y").unwrap(),
371 &Inner {
372 fun: false,
373 explosive: true
374 }
375 );
376 assert_eq!(
377 built.get("zz").unwrap(),
378 &Inner {
379 fun: true,
380 explosive: false
381 }
382 );
383
384 let tm2: ThingMap2Builder = toml::from_str(
385 r#"
386 [x]
387 explosive = true
388 [zz]
389 fun = true
390 "#,
391 )
392 .unwrap();
393 let built2 = tm2.build().unwrap();
394 assert_eq!(built, built2);
395 }
396}