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
    {paste::paste!{
170
        $(#[ $m ])*
171
        $v type $maptype = $coltype < $keytype , $valtype > ;
172

            
173
        $(#[ $b_m ])*
174
424
        #[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
206
            $b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
181
206
                self.0
182
206
                    .iter()
183
505
                    .map(|(k,v)| Ok((k.clone(), v.build()?)))
184
206
                    .collect()
185
206
            }
186
        }
187
        $(
188
            // This section is expanded when we have a defaults_fn().
189
            impl ::std::default::Default for $btype {
190
198
                fn default() -> Self {
191
198
                    Self($defaults)
192
198
                }
193
            }
194
            impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
195
18
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196
18
                where
197
18
                    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
18
                        let deserialized = $coltype::<$keytype, [<$valtype Builder>]>::deserialize(deserializer)?;
202
18
                        let mut defaults = $btype::default();
203
18
                        $crate::extend_builder::ExtendBuilder::extend_from(
204
18
                            &mut defaults,
205
18
                            Self(deserialized),
206
18
                            $crate::extend_builder::ExtendStrategy::ReplaceLists);
207
18
                        Ok(defaults)
208
18
                    }
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
414
                fn default() -> Self {
215
414
                    Self(Default::default())
216
414
                }
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
26
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222
26
                where
223
26
                    D: $crate::deps::serde::Deserializer<'de> {
224
26
                    Ok(Self($coltype::deserialize(deserializer)?))
225
26
                }
226
            }
227
        }}
228
        impl $crate::extend_builder::ExtendBuilder for $btype
229
        {
230
50
            fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
231
50
                $crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
232
50
            }
233
        }
234
    }};
235
    {@if_empty {} {$($x:tt)*}} => {$($x)*};
236
    {@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
237
}
238

            
239
#[cfg(test)]
240
mod 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]
291
fun = true
292
explosive = false
293

            
294
[things.yy]
295
explosive = true
296
fun = 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
}