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 $crate::deps::paste!{$crate::define_map_builder! {
170 $(#[$b_m])*
171 $b_v struct $btype =>
172 $(#[$m])*
173 $v type $maptype = {
174 map: $coltype < $keytype , $valtype >,
175 builder_map: $coltype < $keytype, [<$valtype Builder>] > ,
176 }
177 $( defaults: $defaults; )?
178 }}
179 };
180 // This _undocumented_ internal syntax allows us to take the map types explicitly,
181 // so that we can accept derive-deftly outputs.
182 //
183 // Syntax:
184 // ```
185 // «#[builder_attrs]»
186 // «vis» struct «FooMapBuilder» =>
187 // «#[maptype_attrs]»
188 // «vis» type «FooMap» = {
189 // map: «maptype»,
190 // builder_map: «builder_map_type»,
191 // }
192 // ⟦ defaults: «default_expr» ; ⟧
193 // ```
194 //
195 // The defaults line and the attributes are optional.
196 // This (undocumented) syntax is identical to the documented variant above,
197 // except in the braced section after the `=` and before the optional `defaults`.
198 // In that section,
199 // the `maptype` is the type of the map which we are trying to build,
200 // (for example, `HashMap<String, WombatCfg>`),
201 // and the `builder_map` is the type of the map which instantiates the builder
202 // (for example, `HashMap<String, WombatCfgBuilder>`).
203 {
204 $(#[ $b_m:meta ])*
205 $b_v:vis struct $btype:ident =>
206 $(#[ $m:meta ])*
207 $v:vis type $maptype:ident = {
208 map: $mtype:ty,
209 builder_map: $bmtype:ty $(,)?
210 }
211 $( defaults: $defaults:expr; )?
212 } =>
213 {$crate::deps::paste!{
214 $(#[ $m ])*
215 $v type $maptype = $mtype ;
216
217 $(#[ $b_m ])*
218 #[derive(Clone,Debug,$crate::deps::serde::Serialize, $crate::deps::educe::Educe)]
219 #[educe(Deref, DerefMut)]
220 #[serde(transparent)]
221 $b_v struct $btype( $bmtype );
222
223 impl $btype {
224 /// Construct the final map.
225 $b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
226 self.0
227 .iter()
228 .map(|(k,v)| Ok((k.clone(), v.build()?)))
229 .collect()
230 }
231 }
232 $(
233 // This section is expanded when we have a defaults_fn().
234 impl ::std::default::Default for $btype {
235 fn default() -> Self {
236 Self($defaults)
237 }
238 }
239 impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
240 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241 where
242 D: $crate::deps::serde::Deserializer<'de> {
243 use $crate::deps::serde::Deserialize;
244 // To deserialize into this type, we create a builder holding the defaults,
245 // and we create a builder holding the values from the deserializer.
246 // We then use ExtendBuilder to extend the defaults with the deserialized values.
247 let deserialized = <$bmtype as Deserialize>::deserialize(deserializer)?;
248 let mut defaults = $btype::default();
249 $crate::extend_builder::ExtendBuilder::extend_from(
250 &mut defaults,
251 Self(deserialized),
252 $crate::extend_builder::ExtendStrategy::ReplaceLists);
253 Ok(defaults)
254 }
255 }
256 )?
257 $crate::define_map_builder!{@if_empty { $($defaults)? } {
258 // This section is expanded when we don't have a defaults_fn().
259 impl ::std::default::Default for $btype {
260 fn default() -> Self {
261 Self(Default::default())
262 }
263 }
264 // We can't conditionally derive() here, since Rust doesn't like macros that expand to
265 // attributes.
266 impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
267 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
268 where
269 D: $crate::deps::serde::Deserializer<'de> {
270 use $crate::deps::serde::Deserialize;
271 Ok(Self(<$bmtype as Deserialize>::deserialize(deserializer)?))
272 }
273 }
274 }}
275 impl $crate::extend_builder::ExtendBuilder for $btype
276 {
277 fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
278 $crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
279 }
280 }
281 }};
282 {@if_empty {} {$($x:tt)*}} => {$($x)*};
283 {@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
284}
285
286#[cfg(test)]
287mod test {
288 // @@ begin test lint list maintained by maint/add_warning @@
289 #![allow(clippy::bool_assert_comparison)]
290 #![allow(clippy::clone_on_copy)]
291 #![allow(clippy::dbg_macro)]
292 #![allow(clippy::mixed_attributes_style)]
293 #![allow(clippy::print_stderr)]
294 #![allow(clippy::print_stdout)]
295 #![allow(clippy::single_char_pattern)]
296 #![allow(clippy::unwrap_used)]
297 #![allow(clippy::unchecked_time_subtraction)]
298 #![allow(clippy::useless_vec)]
299 #![allow(clippy::needless_pass_by_value)]
300 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
301
302 use crate::ConfigBuildError;
303 use derive_builder::Builder;
304 use derive_deftly::Deftly;
305 use serde::{Deserialize, Serialize};
306 use std::collections::BTreeMap;
307
308 #[derive(Clone, Debug, Eq, PartialEq, Builder)]
309 #[builder(derive(Deserialize, Serialize, Debug))]
310 #[builder(build_fn(error = "ConfigBuildError"))]
311 struct Outer {
312 #[builder(sub_builder(fn_name = "build"))]
313 #[builder_field_attr(serde(default))]
314 things: ThingMap,
315 }
316
317 #[derive(Clone, Debug, Eq, PartialEq, Builder, Deftly)]
318 #[derive_deftly(ExtendBuilder)]
319 #[builder(derive(Deserialize, Serialize, Debug))]
320 #[builder(build_fn(error = "ConfigBuildError"))]
321 struct Inner {
322 #[builder(default)]
323 fun: bool,
324 #[builder(default)]
325 explosive: bool,
326 }
327
328 define_map_builder! {
329 struct ThingMapBuilder =>
330 type ThingMap = BTreeMap<String, Inner>;
331 }
332
333 #[test]
334 fn parse_and_build() {
335 let builder: OuterBuilder = toml::from_str(
336 r#"
337[things.x]
338fun = true
339explosive = false
340
341[things.yy]
342explosive = true
343fun = true
344"#,
345 )
346 .unwrap();
347
348 let built = builder.build().unwrap();
349 assert_eq!(
350 built.things.get("x").unwrap(),
351 &Inner {
352 fun: true,
353 explosive: false
354 }
355 );
356 assert_eq!(
357 built.things.get("yy").unwrap(),
358 &Inner {
359 fun: true,
360 explosive: true
361 }
362 );
363 }
364
365 #[test]
366 fn build_directly() {
367 let mut builder = OuterBuilder::default();
368 let mut bld = InnerBuilder::default();
369 bld.fun(true);
370 builder.things().insert("x".into(), bld);
371 let built = builder.build().unwrap();
372 assert_eq!(
373 built.things.get("x").unwrap(),
374 &Inner {
375 fun: true,
376 explosive: false
377 }
378 );
379 }
380
381 define_map_builder! {
382 struct ThingMap2Builder =>
383 type ThingMap2 = BTreeMap<String, Inner>;
384 defaults: thingmap2_default();
385 }
386 fn thingmap2_default() -> BTreeMap<String, InnerBuilder> {
387 let mut map = BTreeMap::new();
388 {
389 let mut bld = InnerBuilder::default();
390 bld.fun(true);
391 map.insert("x".to_string(), bld);
392 }
393 {
394 let mut bld = InnerBuilder::default();
395 bld.explosive(true);
396 map.insert("y".to_string(), bld);
397 }
398 map
399 }
400 #[test]
401 fn with_defaults() {
402 let mut tm2 = ThingMap2Builder::default();
403 tm2.get_mut("x").unwrap().explosive(true);
404 let mut bld = InnerBuilder::default();
405 bld.fun(true);
406 tm2.insert("zz".into(), bld);
407 let built = tm2.build().unwrap();
408
409 assert_eq!(
410 built.get("x").unwrap(),
411 &Inner {
412 fun: true,
413 explosive: true
414 }
415 );
416 assert_eq!(
417 built.get("y").unwrap(),
418 &Inner {
419 fun: false,
420 explosive: true
421 }
422 );
423 assert_eq!(
424 built.get("zz").unwrap(),
425 &Inner {
426 fun: true,
427 explosive: false
428 }
429 );
430
431 let tm2: ThingMap2Builder = toml::from_str(
432 r#"
433 [x]
434 explosive = true
435 [zz]
436 fun = true
437 "#,
438 )
439 .unwrap();
440 let built2 = tm2.build().unwrap();
441 assert_eq!(built, built2);
442 }
443}