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_deftly::Deftly;
65
/// # use std::collections::BTreeMap;
66
/// # use tor_config::{ConfigBuildError, define_map_builder, derive_deftly_template_ExtendBuilder};
67
/// # use tor_config::derive::prelude::*;
68
/// # use serde::{Serialize, Deserialize};
69
/// # use tor_config::extend_builder::{ExtendBuilder,ExtendStrategy};
70
/// #[derive(Clone, Debug, Deftly, Eq, PartialEq)]
71
/// #[derive_deftly(TorConfig)]
72
/// pub struct ConnectionsConfig {
73
///     // Note: Ordinarily, you might choose to `tor_config(map)` instead,
74
///     // to automate more of this derivation.
75
///     #[deftly(tor_config(sub_builder, no_magic))]
76
///     conns: ConnectionMap
77
/// }
78
///
79
/// define_map_builder! {
80
///     pub struct ConnectionMapBuilder =>
81
///     pub type ConnectionMap = BTreeMap<String, ConnConfig>;
82
/// }
83
///
84
/// #[derive(Clone, Debug, Deftly, Eq, PartialEq)]
85
/// #[derive_deftly(TorConfig)]
86
/// pub struct ConnConfig {
87
///     #[deftly(tor_config(default="true"))]
88
///     enabled: bool,
89
///     #[deftly(tor_config(default="9999"))]
90
///     port: u16,
91
/// }
92
///
93
/// let defaults: ConnectionsConfigBuilder = toml::from_str(r#"
94
/// [conns."socks"]
95
/// enabled = true
96
/// port = 9150
97
///
98
/// [conns."http"]
99
/// enabled = false
100
/// port = 1234
101
///
102
/// [conns."wombat"]
103
/// port = 5050
104
/// "#).unwrap();
105
/// let user_settings: ConnectionsConfigBuilder = toml::from_str(r#"
106
/// [conns."http"]
107
/// enabled = false
108
/// [conns."quokka"]
109
/// enabled = true
110
/// port = 9999
111
/// "#).unwrap();
112
///
113
/// let mut cfg = defaults.clone();
114
/// cfg.extend_from(user_settings, ExtendStrategy::ReplaceLists);
115
/// let cfg = cfg.build().unwrap();
116
/// assert_eq!(cfg, ConnectionsConfig {
117
///     conns: vec![
118
///         ("http".into(), ConnConfig { enabled: false, port: 1234}),
119
///         ("quokka".into(), ConnConfig { enabled: true, port: 9999}),
120
///         ("socks".into(), ConnConfig { enabled: true, port: 9150}),
121
///         ("wombat".into(), ConnConfig { enabled: true, port: 5050}),
122
///     ].into_iter().collect(),
123
/// });
124
/// ```
125
///
126
/// In the example above, the `derive_map_builder` macro expands to something like:
127
///
128
/// ```ignore
129
/// pub type ConnectionMap = BTreeMap<String, ConnConfig>;
130
///
131
/// #[derive(Clone,Debug,Serialize,Educe)]
132
/// #[educe(Deref,DerefMut)]
133
/// pub struct ConnectionMapBuilder(BTreeMap<String, ConnConfigBuilder);
134
///
135
/// impl ConnectionMapBuilder {
136
///     fn build(&self) -> Result<ConnectionMap, ConfigBuildError> {
137
///         ...
138
///     }
139
/// }
140
/// impl Default for ConnectionMapBuilder { ... }
141
/// impl Deserialize for ConnectionMapBuilder { ... }
142
/// impl ExtendBuilder for ConnectionMapBuilder { ... }
143
/// ```
144
///
145
/// # Notes and rationale
146
///
147
/// We use this macro, instead of using a Map directly in our configuration object,
148
/// so that we can have a separate Builder type with a reasonable build() implementation.
149
///
150
/// We don't support complicated keys; instead we require that the keys implement Deserialize.
151
/// If we ever need to support keys with their own builders,
152
/// we'll have to define a new macro.
153
///
154
/// We use `ExtendBuilder` to implement Deserialize with defaults,
155
/// so that the provided configuration options can override
156
/// only those parts of the default configuration tree
157
/// that they actually replace.
158
#[macro_export]
159
macro_rules! define_map_builder {
160
    {
161
        $(#[ $b_m:meta ])*
162
        $b_v:vis struct $btype:ident =>
163
        $(#[ $m:meta ])*
164
        $v:vis type $maptype:ident = $coltype:ident < $keytype:ty , $valtype: ty >;
165
        $( defaults: $defaults:expr; )?
166
    } => {
167
        $crate::deps::paste!{$crate::define_map_builder! {
168
            $(#[$b_m])*
169
            $b_v struct $btype =>
170
            $(#[$m])*
171
            $v type $maptype = {
172
                map: $coltype < $keytype , $valtype >,
173
                builder_map: $coltype < $keytype, [<$valtype Builder>] > ,
174
            }
175
            $( defaults: $defaults; )?
176
        }}
177
    };
178
    // This _undocumented_ internal syntax allows us to take the map types explicitly,
179
    // so that we can accept derive-deftly outputs.
180
    //
181
    // Syntax:
182
    // ```
183
    //   «#[builder_attrs]»
184
    //   «vis» struct «FooMapBuilder» =>
185
    //   «#[maptype_attrs]»
186
    //   «vis» type «FooMap» = {
187
    //       map: «maptype»,
188
    //       builder_map: «builder_map_type»,
189
    //   }
190
    //   ⟦ defaults: «default_expr» ; ⟧
191
    // ```
192
    //
193
    // The defaults line and the attributes are optional.
194
    // This (undocumented) syntax is identical to the documented variant above,
195
    // except in the braced section after the `=` and before the optional `defaults`.
196
    // In that section,
197
    // the `maptype` is the type of the map which we are trying to build,
198
    // (for example, `HashMap<String, WombatCfg>`),
199
    // and the `builder_map` is the type of the map which instantiates the builder
200
    // (for example, `HashMap<String, WombatCfgBuilder>`).
201
    {
202
        $(#[ $b_m:meta ])*
203
        $b_v:vis struct $btype:ident =>
204
        $(#[ $m:meta ])*
205
        $v:vis type $maptype:ident = {
206
            map: $mtype:ty,
207
            builder_map: $bmtype:ty $(,)?
208
        }
209
        $( defaults: $defaults:expr; )?
210
    } =>
211
    {$crate::deps::paste!{
212
        $(#[ $m ])*
213
        $v type $maptype = $mtype ;
214

            
215
        $(#[ $b_m ])*
216
        #[derive(Clone,Debug,$crate::deps::serde::Serialize, $crate::deps::educe::Educe)]
217
        #[educe(Deref, DerefMut)]
218
        #[serde(transparent)]
219
        $b_v struct $btype( $bmtype );
220

            
221
        impl $btype {
222
            /// Construct the final map.
223
432
            $b_v fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
224
432
                self.0
225
432
                    .iter()
226
968
                    .map(|(k,v)| Ok((k.clone(), v.build()?)))
227
432
                    .collect()
228
432
            }
229
        }
230
        impl $crate::load::Builder for $btype {
231
            type Built = $maptype;
232
            fn build(&self) -> ::std::result::Result<$maptype, $crate::ConfigBuildError> {
233
                $btype :: build(self)
234
            }
235
        }
236

            
237
        impl $crate::load::ConfigBuilder for $btype {
238
            fn apply_defaults(&mut self) -> ::std::result::Result<(), $crate::ConfigBuildError> {
239
                #[allow(unused_imports)]
240
                use $crate::load::ConfigBuilder as _;
241
                for v in self.0.values_mut() {
242
                    v.apply_defaults()?;
243
                }
244
                Ok(())
245
            }
246
        }
247
        $(
248
            // This section is expanded when we have a defaults_fn().
249
            impl ::std::default::Default for $btype {
250
422
                fn default() -> Self {
251
422
                    Self($defaults)
252
422
                }
253
            }
254
            impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
255
14
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256
14
                where
257
14
                    D: $crate::deps::serde::Deserializer<'de> {
258
                        use $crate::deps::serde::Deserialize;
259
                        // To deserialize into this type, we create a builder holding the defaults,
260
                        // and we create a builder holding the values from the deserializer.
261
                        // We then use ExtendBuilder to extend the defaults with the deserialized values.
262
14
                        let deserialized = <$bmtype as Deserialize>::deserialize(deserializer)?;
263
14
                        let mut defaults = $btype::default();
264
14
                        $crate::extend_builder::ExtendBuilder::extend_from(
265
14
                            &mut defaults,
266
14
                            Self(deserialized),
267
14
                            $crate::extend_builder::ExtendStrategy::ReplaceLists);
268
14
                        Ok(defaults)
269
14
                    }
270
            }
271
        )?
272
        $crate::define_map_builder!{@if_empty { $($defaults)? } {
273
            // This section is expanded when we don't have a defaults_fn().
274
            impl ::std::default::Default for $btype {
275
854
                fn default() -> Self {
276
854
                    Self(Default::default())
277
854
                }
278
            }
279
            // We can't conditionally derive() here, since Rust doesn't like macros that expand to
280
            // attributes.
281
            impl<'de> $crate::deps::serde::Deserialize<'de> for $btype {
282
18
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
283
18
                where
284
18
                    D: $crate::deps::serde::Deserializer<'de> {
285
                    use $crate::deps::serde::Deserialize;
286
18
                    Ok(Self(<$bmtype as Deserialize>::deserialize(deserializer)?))
287
18
                }
288
            }
289
        }}
290
        impl $crate::extend_builder::ExtendBuilder for $btype
291
        {
292
38
            fn extend_from(&mut self, other: Self, strategy: $crate::extend_builder::ExtendStrategy) {
293
38
                $crate::extend_builder::ExtendBuilder::extend_from(&mut self.0, other.0, strategy);
294
38
            }
295
        }
296
    }};
297
    {@if_empty {} {$($x:tt)*}} => {$($x)*};
298
    {@if_empty {$($y:tt)*} {$($x:tt)*}} => {};
299
}
300

            
301
#[cfg(test)]
302
mod test {
303
    // @@ begin test lint list maintained by maint/add_warning @@
304
    #![allow(clippy::bool_assert_comparison)]
305
    #![allow(clippy::clone_on_copy)]
306
    #![allow(clippy::dbg_macro)]
307
    #![allow(clippy::mixed_attributes_style)]
308
    #![allow(clippy::print_stderr)]
309
    #![allow(clippy::print_stdout)]
310
    #![allow(clippy::single_char_pattern)]
311
    #![allow(clippy::unwrap_used)]
312
    #![allow(clippy::unchecked_time_subtraction)]
313
    #![allow(clippy::useless_vec)]
314
    #![allow(clippy::needless_pass_by_value)]
315
    #![allow(clippy::string_slice)] // See arti#2571
316
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
317

            
318
    use crate::derive::prelude::*;
319
    use derive_deftly::Deftly;
320
    use std::collections::BTreeMap;
321

            
322
    #[derive(Clone, Debug, Eq, PartialEq, Deftly)]
323
    #[derive_deftly(TorConfig)]
324
    struct Outer {
325
        #[deftly(tor_config(sub_builder, no_magic))]
326
        things: ThingMap,
327
    }
328

            
329
    #[derive(Clone, Debug, Eq, PartialEq, Deftly)]
330
    #[derive_deftly(TorConfig)]
331
    struct Inner {
332
        #[deftly(tor_config(default))]
333
        fun: bool,
334
        #[deftly(tor_config(default))]
335
        explosive: bool,
336
    }
337

            
338
    impl InnerBuilder {
339
        // Testing only. We don't want to use derive_deftly(TorConfig) on Inner
340
        // because we are trying to test define_map_builder by hand.
341
        #[expect(clippy::unnecessary_wraps)]
342
        fn apply_defaults(&mut self) -> Result<(), crate::ConfigBuildError> {
343
            self.fun.get_or_insert_default();
344
            self.explosive.get_or_insert_default();
345
            Ok(())
346
        }
347
    }
348

            
349
    define_map_builder! {
350
        struct ThingMapBuilder =>
351
        type ThingMap = BTreeMap<String, Inner>;
352
    }
353

            
354
    #[test]
355
    fn parse_and_build() {
356
        let builder: OuterBuilder = toml::from_str(
357
            r#"
358
[things.x]
359
fun = true
360
explosive = false
361

            
362
[things.yy]
363
explosive = true
364
fun = true
365
"#,
366
        )
367
        .unwrap();
368

            
369
        let built = builder.build().unwrap();
370
        assert_eq!(
371
            built.things.get("x").unwrap(),
372
            &Inner {
373
                fun: true,
374
                explosive: false
375
            }
376
        );
377
        assert_eq!(
378
            built.things.get("yy").unwrap(),
379
            &Inner {
380
                fun: true,
381
                explosive: true
382
            }
383
        );
384
    }
385

            
386
    #[test]
387
    fn build_directly() {
388
        let mut builder = OuterBuilder::default();
389
        let mut bld = InnerBuilder::default();
390
        bld.fun(true);
391
        builder.things().insert("x".into(), bld);
392
        let built = builder.build().unwrap();
393
        assert_eq!(
394
            built.things.get("x").unwrap(),
395
            &Inner {
396
                fun: true,
397
                explosive: false
398
            }
399
        );
400
    }
401

            
402
    define_map_builder! {
403
        struct ThingMap2Builder =>
404
        type ThingMap2 = BTreeMap<String, Inner>;
405
        defaults: thingmap2_default();
406
    }
407
    fn thingmap2_default() -> BTreeMap<String, InnerBuilder> {
408
        let mut map = BTreeMap::new();
409
        {
410
            let mut bld = InnerBuilder::default();
411
            bld.fun(true);
412
            map.insert("x".to_string(), bld);
413
        }
414
        {
415
            let mut bld = InnerBuilder::default();
416
            bld.explosive(true);
417
            map.insert("y".to_string(), bld);
418
        }
419
        map
420
    }
421
    #[test]
422
    fn with_defaults() {
423
        let mut tm2 = ThingMap2Builder::default();
424
        tm2.get_mut("x").unwrap().explosive(true);
425
        let mut bld = InnerBuilder::default();
426
        bld.fun(true);
427
        tm2.insert("zz".into(), bld);
428
        let built = tm2.build().unwrap();
429

            
430
        assert_eq!(
431
            built.get("x").unwrap(),
432
            &Inner {
433
                fun: true,
434
                explosive: true
435
            }
436
        );
437
        assert_eq!(
438
            built.get("y").unwrap(),
439
            &Inner {
440
                fun: false,
441
                explosive: true
442
            }
443
        );
444
        assert_eq!(
445
            built.get("zz").unwrap(),
446
            &Inner {
447
                fun: true,
448
                explosive: false
449
            }
450
        );
451

            
452
        let tm2: ThingMap2Builder = toml::from_str(
453
            r#"
454
            [x]
455
            explosive = true
456
            [zz]
457
            fun = true
458
            "#,
459
        )
460
        .unwrap();
461
        let built2 = tm2.build().unwrap();
462
        assert_eq!(built, built2);
463
    }
464
}