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]
161
macro_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
404
            $b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
226
404
                self.0
227
404
                    .iter()
228
914
                    .map(|(k,v)| Ok((k.clone(), v.build()?)))
229
404
                    .collect()
230
404
            }
231
        }
232
        $(
233
            // This section is expanded when we have a defaults_fn().
234
            impl ::std::default::Default for $btype {
235
398
                fn default() -> Self {
236
398
                    Self($defaults)
237
398
                }
238
            }
239
            impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
240
14
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241
14
                where
242
14
                    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
14
                        let deserialized = <$bmtype as Deserialize>::deserialize(deserializer)?;
248
14
                        let mut defaults = $btype::default();
249
14
                        $crate::extend_builder::ExtendBuilder::extend_from(
250
14
                            &mut defaults,
251
14
                            Self(deserialized),
252
14
                            $crate::extend_builder::ExtendStrategy::ReplaceLists);
253
14
                        Ok(defaults)
254
14
                    }
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
802
                fn default() -> Self {
261
802
                    Self(Default::default())
262
802
                }
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
18
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
268
18
                where
269
18
                    D: $crate::deps::serde::Deserializer<'de> {
270
                    use $crate::deps::serde::Deserialize;
271
18
                    Ok(Self(<$bmtype as Deserialize>::deserialize(deserializer)?))
272
18
                }
273
            }
274
        }}
275
        impl $crate::extend_builder::ExtendBuilder for $btype
276
        {
277
38
            fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
278
38
                $crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
279
38
            }
280
        }
281
    }};
282
    {@if_empty {} {$($x:tt)*}} => {$($x)*};
283
    {@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
284
}
285

            
286
#[cfg(test)]
287
mod 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]
338
fun = true
339
explosive = false
340

            
341
[things.yy]
342
explosive = true
343
fun = 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
}