1
//! Processing a `ConfigurationTree` into a validated configuration
2
//!
3
//! This module, and particularly [`resolve`], takes care of:
4
//!
5
//!   * Deserializing a [`ConfigurationTree`] into various `FooConfigBuilder`
6
//!   * Calling the `build()` methods to get various `FooConfig`.
7
//!   * Reporting unrecognised configuration keys
8
//!     (eg to help the user detect misspellings).
9
//!
10
//! This is step 3 of the overall config processing,
11
//! as described in the [crate-level documentation](crate).
12
//!
13
//! # Starting points
14
//!
15
//! To use this, you will need to:
16
//!
17
//!   * `#[derive(Builder)]` and use [`impl_standard_builder!`](crate::impl_standard_builder)
18
//!     for all of your configuration structures,
19
//!     using `#[sub_builder]` etc. sa appropriate,
20
//!     and making your builders [`Deserialize`](serde::Deserialize).
21
//!
22
//!   * [`impl TopLevel`](TopLevel) for your *top level* structures (only).
23
//!
24
//!   * Call [`resolve`] (or one of its variants) with a `ConfigurationTree`,
25
//!     to obtain your top-level configuration(s).
26
//!
27
//! # Example
28
//!
29
//! In this example the developers are embedding `arti`, `arti_client`, etc.,
30
//! into a program of their own.  The example code shown:
31
//!
32
//!  * Defines a configuration structure `EmbedderConfig`,
33
//!    for additional configuration settings for the added features.
34
//!  * Establishes some configuration sources
35
//!    (the trivial empty [`ConfigurationSources`](crate::ConfigurationSources),
36
//!    to avoid clutter in the example)
37
//!  * Reads those sources into a single configuration taxonomy [`ConfigurationTree`].
38
//!  * Processes that configuration into a 3-tuple of configuration
39
//!    structs for the three components, namely:
40
//!      - `TorClientConfig`, the configuration for the `arti_client` crate's `TorClient`
41
//!      - `ArtiConfig`, for behaviours in the `arti` command line utility
42
//!      - `EmbedderConfig`.
43
//!  * Will report a warning to the user about config settings found in the config files,
44
//!    but not recognized by *any* of the three config consumers,
45
//!
46
//! ```
47
//! # fn main() -> Result<(), tor_config::load::ConfigResolveError> {
48
//! use derive_builder::Builder;
49
//! use tor_config::{impl_standard_builder, resolve, ConfigBuildError, ConfigurationSources};
50
//! use tor_config::load::TopLevel;
51
//! use tor_config::derive::prelude::*;
52
//! use serde::{Deserialize, Serialize};
53
//! use derive_deftly::Deftly;
54
//!
55
//! #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
56
//! #[derive_deftly(TorConfig)]
57
//! struct EmbedderConfig {
58
//!     // ....
59
//! }
60
//! impl TopLevel for EmbedderConfig {
61
//!     type Builder = EmbedderConfigBuilder;
62
//! }
63
//! #
64
//! # #[derive(Debug, Clone, Builder, Eq, PartialEq)]
65
//! # #[builder(build_fn(error = "ConfigBuildError"))]
66
//! # #[builder(derive(Debug, Serialize, Deserialize))]
67
//! # struct TorClientConfig { }
68
//! # impl_standard_builder! { TorClientConfig }
69
//! # impl TopLevel for TorClientConfig { type Builder = TorClientConfigBuilder; }
70
//! # impl tor_config::load::ConfigBuilder for TorClientConfigBuilder {
71
//! #     fn apply_defaults(&mut self) -> Result<(), ConfigBuildError> { Ok(()) }
72
//! # }
73
//! #
74
//! # #[derive(Debug, Clone, Builder, Eq, PartialEq)]
75
//! # #[builder(build_fn(error = "ConfigBuildError"))]
76
//! # #[builder(derive(Debug, Serialize, Deserialize))]
77
//! # struct ArtiConfig { }
78
//! # impl_standard_builder! { ArtiConfig }
79
//! # impl TopLevel for ArtiConfig { type Builder = ArtiConfigBuilder; }
80
//! # impl tor_config::load::ConfigBuilder for ArtiConfigBuilder {
81
//! #     fn apply_defaults(&mut self) -> Result<(), ConfigBuildError> { Ok(()) }
82
//! # }
83

            
84
//!
85
//! let cfg_sources = ConfigurationSources::new_empty(); // In real program, use from_cmdline
86
//! let cfg = cfg_sources.load()?;
87
//!
88
//! let (tcc, arti_config, embedder_config) =
89
//!      tor_config::resolve::<(TorClientConfig, ArtiConfig, EmbedderConfig)>(cfg)?;
90
//!
91
//! let _: EmbedderConfig = embedder_config; // etc.
92
//!
93
//! # Ok(())
94
//! # }
95
//! ```
96

            
97
use std::collections::BTreeSet;
98
use std::fmt::{self, Display};
99
use std::iter;
100
use std::mem;
101

            
102
use itertools::{Itertools, chain, izip};
103
use serde::de::DeserializeOwned;
104
use thiserror::Error;
105
use tracing::warn;
106

            
107
use crate::{ConfigBuildError, ConfigurationTree};
108

            
109
/// Error resolving a configuration (during deserialize, or build)
110
#[derive(Error, Debug)]
111
#[non_exhaustive]
112
pub enum ConfigResolveError {
113
    /// Deserialize failed
114
    #[error("Config contents not as expected")]
115
    Deserialize(#[from] crate::ConfigError),
116

            
117
    /// Build failed
118
    #[error("Config semantically incorrect")]
119
    Build(#[from] ConfigBuildError),
120
}
121

            
122
/// A type that can be built from a builder.
123
pub trait Buildable {
124
    /// The type that constructs this Buildable.
125
    ///
126
    /// Typically, this type will implement [`Builder`].
127
    /// If it does, then `<Self::Builder>::Built` should be `Self`.
128
    type Builder;
129

            
130
    /// Return a new Builder for this type.
131
    fn builder() -> Self::Builder;
132
}
133

            
134
/// A type that can build some buildable type via a build method.
135
pub trait Builder {
136
    /// The type that this builder constructs.
137
    ///
138
    /// Typically, this type will implement [`Buildable`].
139
    /// If it does, then `<Self::Built as Buildable>::Builder` should be `Self`.
140
    type Built;
141

            
142
    /// Build into a `Built`
143
    ///
144
    /// Often shadows an inherent `build` method
145
    fn build(&self) -> Result<Self::Built, ConfigBuildError>;
146
}
147

            
148
/// A [`Builder`] as generated and used by our configuration system.
149
//
150
// (This is a separate type from Builder since we also implement Builder for some
151
// non-configuration builders.)
152
pub trait ConfigBuilder: Builder {
153
    /// Modify `self` by replacing any options that haven't been set with their defaults.
154
    ///
155
    /// Also, resolves deprecated settings:
156
    /// When a deprecated setting is provided, and the modern version is not,
157
    /// `apply_defaults` derives the modern value from the deprecated value,
158
    /// and writes it into the modern field.
159
    /// (If both a deprecated setting, and its modern form, are set
160
    /// on entry to `apply_defaults`, the deprecated form is ignored.)
161
    ///
162
    /// It is not necessary to call this method
163
    /// if all you want to do with this builder
164
    /// is to call [`build()`](Builder::build) on it:
165
    /// `build()` also takes defaults into account.
166
    fn apply_defaults(&mut self) -> Result<(), ConfigBuildError>;
167
}
168

            
169
/// Collection of configuration settings that can be deserialized and then built
170
///
171
/// *Do not implement directly.*
172
/// Instead, implement [`TopLevel`]: doing so engages the blanket impl
173
/// for (loosely) `TopLevel + Builder`.
174
///
175
/// Each `Resolvable` corresponds to one or more configuration consumers.
176
///
177
/// Ultimately, one `Resolvable` for all the configuration consumers in an entire
178
/// program will be resolved from a single configuration tree (usually parsed from TOML).
179
///
180
/// Multiple config collections can be resolved from the same configuration,
181
/// via the implementation of `Resolvable` on tuples of `Resolvable`s.
182
/// Use this rather than `#[serde(flatten)]`; the latter prevents useful introspection
183
/// (necessary for reporting unrecognized configuration keys, and testing).
184
///
185
/// (The `resolve` method will be called only from within the `tor_config::load` module.)
186
pub trait Resolvable: Sized {
187
    /// Deserialize and build from a configuration
188
    //
189
    // Implementations must do the following:
190
    //
191
    //  1. Deserializes the input (cloning it to be able to do this)
192
    //     into the `Builder`.
193
    //
194
    //  2. Having used `serde_ignored` to detect unrecognized keys,
195
    //     intersects those with the unrecognized keys recorded in the context.
196
    //
197
    //  3. Calls `build` on the `Builder` to get `Self`.
198
    //
199
    // We provide impls for TopLevels, and tuples of Resolvable.
200
    //
201
    // Cannot be implemented outside this module (except eg as a wrapper or something),
202
    // because that would somehow involve creating `Self` from `ResolveContext`
203
    // but `ResolveContext` is completely opaque outside this module.
204
    fn resolve(input: &mut ResolveContext) -> Result<Self, ConfigResolveError>;
205

            
206
    /// Return a list of deprecated config keys, as "."-separated strings
207
    fn enumerate_deprecated_keys<F>(f: &mut F)
208
    where
209
        F: FnMut(&'static [&'static str]);
210
}
211

            
212
/// Top-level configuration struct, made from a deserializable builder
213
///
214
/// One configuration consumer's configuration settings.
215
///
216
/// Implementing this trait only for top-level configurations,
217
/// which are to be parsed at the root level of a (TOML) config file taxonomy.
218
///
219
/// This trait exists to:
220
///
221
///  * Mark the toplevel configuration structures as suitable for use with [`resolve`]
222
///  * Provide the type of the `Builder` for use by Rust generic code
223
pub trait TopLevel {
224
    /// The `Builder` which can be used to make a `Self`
225
    ///
226
    /// Should satisfy `&'_ Self::Builder: Builder<Built=Self>`
227
    type Builder: DeserializeOwned;
228

            
229
    /// Deprecated config keys, as "."-separates strings
230
    const DEPRECATED_KEYS: &'static [&'static str] = &[];
231
}
232

            
233
/// `impl Resolvable for (A,B..) where A: Resolvable, B: Resolvable ...`
234
///
235
/// The implementation simply calls `Resolvable::resolve` for each output tuple member.
236
///
237
/// `define_for_tuples!{ A B - C D.. }`
238
///
239
/// expands to
240
///  1. `define_for_tuples!{ A B - }`: defines for tuple `(A,B,)`
241
///  2. `define_for_tuples!{ A B C - D.. }`: recurses to generate longer tuples
242
macro_rules! define_for_tuples {
243
    { $( $A:ident )* - $B:ident $( $C:ident )* } => {
244
        define_for_tuples!{ $($A)* - }
245
        define_for_tuples!{ $($A)* $B - $($C)* }
246
    };
247
    { $( $A:ident )* - } => {
248
        impl < $($A,)* > Resolvable for ( $($A,)* )
249
        where $( $A: Resolvable, )*
250
        {
251
366
            fn resolve(cfg: &mut ResolveContext) -> Result<Self, ConfigResolveError> {
252
364
                Ok(( $( $A::resolve(cfg)?, )* ))
253
366
            }
254
366
            fn enumerate_deprecated_keys<NF>(f: &mut NF)
255
366
            where NF: FnMut(&'static [&'static str]) {
256
366
                $( $A::enumerate_deprecated_keys(f); )*
257
366
            }
258
        }
259

            
260
    };
261
}
262
// We could avoid recursion by writing out A B C... several times (in a "triangle") but this
263
// would make it tiresome and error-prone to extend the impl to longer tuples.
264
define_for_tuples! { A - B C D E }
265

            
266
/// Config resolution context, not used outside `tor_config::load`
267
///
268
/// This is public only because it appears in the [`Resolvable`] trait.
269
/// You don't want to try to obtain one.
270
pub struct ResolveContext {
271
    /// The input
272
    input: ConfigurationTree,
273

            
274
    /// Paths unrecognized by all deserializations
275
    ///
276
    /// None means we haven't deserialized anything yet, ie means the universal set.
277
    ///
278
    /// Empty is used to disable this feature.
279
    unrecognized: UnrecognizedKeys,
280

            
281
    /// If present, a [`ConfigurationTree`] to receive all recognized or defaulted
282
    /// settings from the input tree.
283
    output_tree: Option<ConfigurationTree>,
284
}
285

            
286
/// Keys we have *not* recognized so far
287
///
288
/// Initially `AllKeys`, since we haven't recognized any.
289
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
290
enum UnrecognizedKeys {
291
    /// No keys have yet been recognized, so everything in the config is unrecognized
292
    AllKeys,
293

            
294
    /// The keys which remain unrecognized by any consumer
295
    ///
296
    /// If this is empty, we do not (need to) do any further tracking.
297
    These(BTreeSet<DisfavouredKey>),
298
}
299
use UnrecognizedKeys as UK;
300

            
301
impl UnrecognizedKeys {
302
    /// Does it represent the empty set
303
8680
    fn is_empty(&self) -> bool {
304
8680
        match self {
305
4461
            UK::AllKeys => false,
306
4219
            UK::These(ign) => ign.is_empty(),
307
        }
308
8680
    }
309

            
310
    /// Update in place, intersecting with `other`
311
8304
    fn intersect_with(&mut self, other: BTreeSet<DisfavouredKey>) {
312
8304
        match self {
313
4459
            UK::AllKeys => *self = UK::These(other),
314
3845
            UK::These(self_) => {
315
3845
                let tign = mem::take(self_);
316
3845
                *self_ = intersect_unrecognized_lists(tign, other);
317
3845
            }
318
        }
319
8304
    }
320

            
321
    /// Remove every element of this set.
322
6
    fn clear(&mut self) {
323
6
        *self = UK::These(BTreeSet::new());
324
6
    }
325
}
326

            
327
/// Key in config file(s) which is disfavoured (unrecognized or deprecated)
328
///
329
/// [`Display`]s in an approximation to TOML format.
330
/// You can use the [`to_string()`](ToString::to_string) method to obtain
331
/// a string containing a TOML key path.
332
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
333
pub struct DisfavouredKey {
334
    /// Can be empty only before returned from this module
335
    path: Vec<PathEntry>,
336
}
337

            
338
/// Element of an DisfavouredKey
339
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
340
enum PathEntry {
341
    /// Array index
342
    ///
343
    ArrayIndex(usize),
344
    /// Map entry
345
    ///
346
    /// string value is unquoted, needs quoting for display
347
    MapEntry(String),
348
}
349

            
350
/// A set of options to use for resolving a configuration tree.
351
///
352
/// These options should not affect the actual configuration objects returned
353
/// by [`resolve_return_results`], though they may affect other elements
354
/// of [`ResolutionResults`].
355
#[derive(Clone, Debug)]
356
#[non_exhaustive]
357
pub struct ConfigResolveOptions {
358
    /// If true, we should keep track of which deprecated keys were used.
359
    want_disfavoured: bool,
360

            
361
    /// If true, we should return an output_tree value containing the
362
    /// all the settings that we used to build the configuration,
363
    /// including the default values for any settings that were not present
364
    /// in the input.
365
    pub want_output_tree: bool,
366
}
367

            
368
impl Default for ConfigResolveOptions {
369
494
    fn default() -> Self {
370
494
        Self {
371
494
            want_disfavoured: true,
372
494
            want_output_tree: false,
373
494
        }
374
494
    }
375
}
376

            
377
/// Deserialize and build overall configuration from config sources
378
///
379
/// Inner function used by all the `resolve_*` family
380
374
fn resolve_inner<T>(
381
374
    input: ConfigurationTree,
382
374
    options: &ConfigResolveOptions,
383
374
) -> Result<ResolutionResults<T>, ConfigResolveError>
384
374
where
385
374
    T: Resolvable,
386
{
387
374
    let mut deprecated = BTreeSet::new();
388

            
389
374
    if options.want_disfavoured {
390
736
        T::enumerate_deprecated_keys(&mut |l: &[&str]| {
391
736
            for key in l {
392
718
                match input.0.find_value(key) {
393
716
                    Err(_) => {}
394
2
                    Ok(_) => {
395
2
                        deprecated.insert(key);
396
2
                    }
397
                }
398
            }
399
736
        });
400
2
    }
401

            
402
374
    let mut lc = ResolveContext {
403
374
        input,
404
374
        unrecognized: if options.want_disfavoured {
405
372
            UK::AllKeys
406
        } else {
407
2
            UK::These(BTreeSet::new())
408
        },
409

            
410
374
        output_tree: if options.want_output_tree {
411
            Some(ConfigurationTree::default())
412
        } else {
413
374
            None
414
        },
415
    };
416

            
417
374
    let value = Resolvable::resolve(&mut lc)?;
418

            
419
370
    let unrecognized = match lc.unrecognized {
420
        UK::AllKeys => panic!("all unrecognized, as if we had processed nothing"),
421
370
        UK::These(ign) => ign,
422
    }
423
370
    .into_iter()
424
370
    .filter(|ip| !ip.path.is_empty())
425
370
    .collect_vec();
426

            
427
370
    let deprecated = deprecated
428
370
        .into_iter()
429
370
        .map(|key| {
430
2
            let path = key
431
2
                .split('.')
432
2
                .map(|e| PathEntry::MapEntry(e.into()))
433
2
                .collect_vec();
434
2
            DisfavouredKey { path }
435
2
        })
436
370
        .collect_vec();
437

            
438
370
    Ok(ResolutionResults {
439
370
        value,
440
370
        unrecognized,
441
370
        deprecated,
442
370
        output_tree: lc.output_tree,
443
370
    })
444
374
}
445

            
446
/// Deserialize and build overall configuration from config sources
447
///
448
/// Unrecognized config keys are reported as log warning messages.
449
///
450
/// Resolve the whole configuration in one go, using the `Resolvable` impl on `(A,B)`
451
/// if necessary, so that unrecognized config key processing works correctly.
452
///
453
/// This performs step 3 of the overall config processing,
454
/// as described in the [`tor_config` crate-level documentation](crate).
455
///
456
/// For an example, see the
457
/// [`tor_config::load` module-level documentation](self).
458
350
pub fn resolve<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
459
350
where
460
350
    T: Resolvable,
461
{
462
350
    let options: ConfigResolveOptions = ConfigResolveOptions {
463
350
        want_disfavoured: true,
464
350
        want_output_tree: false,
465
350
    };
466
    let ResolutionResults {
467
350
        value,
468
350
        unrecognized,
469
350
        deprecated,
470
        ..
471
350
    } = resolve_inner(input, &options)?;
472
350
    for depr in deprecated {
473
        warn!("deprecated configuration key: {}", &depr);
474
    }
475
350
    for ign in unrecognized {
476
        warn!("unrecognized configuration key: {}", &ign);
477
    }
478
350
    Ok(value)
479
350
}
480

            
481
/// Deserialize and build overall configuration, reporting unrecognized keys in the return value
482
22
pub fn resolve_return_results<T>(
483
22
    input: ConfigurationTree,
484
22
    options: &ConfigResolveOptions,
485
22
) -> Result<ResolutionResults<T>, ConfigResolveError>
486
22
where
487
22
    T: Resolvable,
488
{
489
22
    resolve_inner(input, options)
490
22
}
491

            
492
/// Results of a successful [`resolve_return_results`].
493
#[derive(Debug, Clone)]
494
#[non_exhaustive]
495
pub struct ResolutionResults<T> {
496
    /// The configuration, successfully parsed
497
    pub value: T,
498

            
499
    /// Any config keys which were found in the input, but not recognized (and so, ignored)
500
    pub unrecognized: Vec<DisfavouredKey>,
501

            
502
    /// Any config keys which were found, but have been declared deprecated
503
    pub deprecated: Vec<DisfavouredKey>,
504

            
505
    /// If present, a [`ConfigurationTree`] with all recognized settings and defaulted settings
506
    /// from the original input.
507
    ///
508
    /// This will be `None` unless `want_output_tree` was set in [`ConfigResolveOptions`].
509
    pub output_tree: Option<ConfigurationTree>,
510
}
511

            
512
/// Deserialize and build overall configuration, silently ignoring unrecognized config keys
513
2
pub fn resolve_ignore_warnings<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
514
2
where
515
2
    T: Resolvable,
516
{
517
2
    let options: ConfigResolveOptions = ConfigResolveOptions {
518
2
        want_disfavoured: false,
519
2
        want_output_tree: false,
520
2
    };
521
2
    Ok(resolve_inner(input, &options)?.value)
522
2
}
523

            
524
/// Wrapper around T that collects ignored keys as we deserialize it.
525
///
526
/// (We need a helper type here since figment does not expose a `Deserializer`
527
/// implementation directly.)
528
struct Des<T> {
529
    /// A set of the ignored keys that we found
530
    nign: BTreeSet<DisfavouredKey>,
531
    /// The underlying value we're deserializing.
532
    value: T,
533
}
534
impl<'de, T> serde::Deserialize<'de> for Des<T>
535
where
536
    T: serde::Deserialize<'de>,
537
{
538
726
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
539
726
    where
540
726
        D: serde::Deserializer<'de>,
541
    {
542
726
        let mut nign = BTreeSet::new();
543
1818
        let mut recorder = |path: serde_ignored::Path<'_>| {
544
1814
            nign.insert(copy_path(&path));
545
1814
        };
546
726
        let deser = serde_ignored::Deserializer::new(deserializer, &mut recorder);
547
726
        let ret = serde::Deserialize::deserialize(deser);
548
726
        Ok(Des { nign, value: ret? })
549
726
    }
550
}
551

            
552
impl<T> Resolvable for T
553
where
554
    T: TopLevel,
555
    T::Builder: Builder<Built = Self> + ConfigBuilder + Clone + serde::Serialize,
556
{
557
742
    fn resolve(input: &mut ResolveContext) -> Result<T, ConfigResolveError> {
558
742
        let deser = &input.input;
559
742
        let builder: Result<T::Builder, _> = {
560
            // If input.unrecognized.is_empty() then we don't bother tracking the
561
            // unrecognized keys since we would intersect with the empty set.
562
            // That is how this tracking is disabled when we want it to be.
563
742
            let want_unrecognized = !input.unrecognized.is_empty();
564
742
            if !want_unrecognized {
565
16
                deser.0.extract_lossy()
566
            } else {
567
726
                let ret: Result<Des<<T as TopLevel>::Builder>, _> = deser.0.extract_lossy();
568

            
569
726
                match ret {
570
720
                    Ok(Des { nign, value }) => {
571
720
                        input.unrecognized.intersect_with(nign);
572
720
                        Ok(value)
573
                    }
574
6
                    Err(e) => {
575
                        // If we got an error, the config might only have been partially processed,
576
                        // so we might get false positives.  Disable the unrecognized tracking.
577
6
                        input.unrecognized.clear();
578
6
                        Err(e)
579
                    }
580
                }
581
            }
582
        };
583
742
        let builder = builder.map_err(crate::ConfigError::from_cfg_err)?;
584

            
585
736
        if let Some(output_tree) = input.output_tree.as_mut() {
586
            let mut with_defaults = builder.clone();
587
            with_defaults.apply_defaults()?;
588
            output_tree.merge_from(&with_defaults)?;
589
736
        }
590

            
591
736
        let built = builder.build()?;
592
736
        Ok(built)
593
742
    }
594

            
595
740
    fn enumerate_deprecated_keys<NF>(f: &mut NF)
596
740
    where
597
740
        NF: FnMut(&'static [&'static str]),
598
    {
599
740
        f(T::DEPRECATED_KEYS);
600
740
    }
601
}
602

            
603
/// Turns a [`serde_ignored::Path`] (which is borrowed) into an owned `DisfavouredKey`
604
21305
fn copy_path(mut path: &serde_ignored::Path) -> DisfavouredKey {
605
    use PathEntry as PE;
606
    use serde_ignored::Path as SiP;
607

            
608
21305
    let mut descend = vec![];
609
    loop {
610
51638
        let (new_path, ent) = match path {
611
21305
            SiP::Root => break,
612
            SiP::Seq { parent, index } => (parent, Some(PE::ArrayIndex(*index))),
613
30333
            SiP::Map { parent, key } => (parent, Some(PE::MapEntry(key.clone()))),
614
            SiP::Some { parent }
615
            | SiP::NewtypeStruct { parent }
616
            | SiP::NewtypeVariant { parent } => (parent, None),
617
        };
618
30333
        descend.extend(ent);
619
30333
        path = new_path;
620
    }
621
21305
    descend.reverse();
622
21305
    DisfavouredKey { path: descend }
623
21305
}
624

            
625
/// Computes the intersection, resolving ignorances at different depths
626
///
627
/// Eg if `a` contains `application.wombat` and `b` contains `application`,
628
/// we need to return `application.wombat`.
629
///
630
/// # Formally
631
///
632
/// A configuration key (henceforth "key") is a sequence of `PathEntry`,
633
/// interpreted as denoting a place in a tree-like hierarchy.
634
///
635
/// Each input `BTreeSet` denotes a subset of the configuration key space.
636
/// Any key in the set denotes itself, but also all possible keys which have it as a prefix.
637
/// We say a s set is "minimal" if it doesn't have entries made redundant by this rule.
638
///
639
/// This function computes a minimal intersection of two minimal inputs.
640
/// If the inputs are not minimal, the output may not be either
641
/// (although `serde_ignored` gives us minimal sets, so that case is not important).
642
3865
fn intersect_unrecognized_lists(
643
3865
    al: BTreeSet<DisfavouredKey>,
644
3865
    bl: BTreeSet<DisfavouredKey>,
645
3865
) -> BTreeSet<DisfavouredKey> {
646
    //eprintln!("INTERSECT:");
647
    //for ai in &al { eprintln!("A: {}", ai); }
648
    //for bi in &bl { eprintln!("B: {}", bi); }
649

            
650
    // This function is written to never talk about "a" and "b".
651
    // That (i) avoids duplication of code for handling a<b vs a>b, etc.
652
    // (ii) make impossible bugs where a was written but b was intended, etc.
653
    // The price is that the result is iterator combinator soup.
654

            
655
7804
    let mut inputs: [_; 2] = [al, bl].map(|input| input.into_iter().peekable());
656
3865
    let mut output = BTreeSet::new();
657

            
658
    // The BTreeSets produce items in sort order.
659
    //
660
    // We maintain the following invariants (valid at the top of the loop):
661
    //
662
    //   For every possible key *strictly earlier* than those remaining in either input,
663
    //   the output contains the key iff it was in the intersection.
664
    //
665
    //   No other keys appear in the output.
666
    //
667
    // We peek at the next two items.  The possible cases are:
668
    //
669
    //   0. One or both inputs is used up.  In that case none of any remaining input
670
    //      can be in the intersection and we are done.
671
    //
672
    //   1. The two inputs have the same next item.  In that case the item is in the
673
    //      intersection.  If the inputs are minimal, no children of that item can appear
674
    //      in either input, so we can make our own output minimal without thinking any
675
    //      more about this item from the point of view of either list.
676
    //
677
    //   2. One of the inputs is a prefix of the other.  In this case the longer item is
678
    //      in the intersection - as are all subsequent items from the same input which
679
    //      also share that prefix.  Then, we must discard the shorter item (which denotes
680
    //      the whole subspace of which only part is in the intersection).
681
    //
682
    //   3. Otherwise, the earlier item is definitely not in the intersection and
683
    //      we can munch it.
684

            
685
    // Peek one from each, while we can.
686
12229
    while let Ok(items) = {
687
        // Ideally we would use array::try_map but it's nightly-only
688
16094
        <[_; 2]>::try_from(
689
16094
            inputs
690
16094
                .iter_mut()
691
32506
                .flat_map(|input: &'_ mut _| input.peek()) // keep the Somes
692
16094
                .collect::<Vec<_>>(), // if we had 2 Somes we can make a [_; 2] from this
693
        )
694
    } {
695
24702
        let shorter_len = items.iter().map(|i| i.path.len()).min().expect("wrong #");
696
12229
        let earlier_i = items
697
12229
            .iter()
698
12229
            .enumerate()
699
12229
            .min_by_key(|&(_i, item)| *item)
700
12229
            .expect("wrong #")
701
            .0;
702
12229
        let later_i = 1 - earlier_i;
703

            
704
12229
        if items.iter().all_equal() {
705
            // Case 0. above.
706
            //
707
            // Take the identical items off the front of both iters,
708
            // and put one into the output (the last will do nicely).
709
            //dbg!(items);
710
26
            let item = inputs
711
26
                .iter_mut()
712
39
                .map(|input| input.next().expect("but peeked"))
713
26
                .next_back()
714
26
                .expect("wrong #");
715
26
            output.insert(item);
716
26
            continue;
717
12203
        } else if items
718
12203
            .iter()
719
24637
            .map(|item| &item.path[0..shorter_len])
720
12203
            .all_equal()
721
        {
722
            // Case 2.  One is a prefix of the other.   earlier_i is the shorter one.
723
8
            let shorter_item = items[earlier_i];
724
8
            let prefix = shorter_item.path.clone(); // borrowck can't prove disjointness
725

            
726
            // Keep copying items from the side with the longer entries,
727
            // so long as they fall within (have the prefix of) the shorter entry.
728
            //dbg!(items, shorter_item, &prefix);
729
24
            while let Some(longer_item) = inputs[later_i].peek() {
730
24
                if !longer_item.path.starts_with(&prefix) {
731
8
                    break;
732
16
                }
733
16
                let longer_item = inputs[later_i].next().expect("but peeked");
734
16
                output.insert(longer_item);
735
            }
736
            // We've "used up" the shorter item.
737
8
            let _ = inputs[earlier_i].next().expect("but peeked");
738
12195
        } else {
739
12195
            // Case 3.  The items are just different.  Eat the earlier one.
740
12195
            //dbg!(items, earlier_i);
741
12195
            let _ = inputs[earlier_i].next().expect("but peeked");
742
12195
        }
743
    }
744
    // Case 0.  At least one of the lists is empty, giving Err() from the array
745

            
746
    //for oi in &ol { eprintln!("O: {}", oi); }
747
3865
    output
748
3865
}
749

            
750
impl Display for DisfavouredKey {
751
18
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
752
        use PathEntry as PE;
753
18
        if self.path.is_empty() {
754
            // shouldn't happen with calls outside this module, and shouldn't be used inside
755
            // but handle it anyway
756
2
            write!(f, r#""""#)?;
757
        } else {
758
16
            let delims = chain!(iter::once(""), iter::repeat("."));
759
22
            for (delim, ent) in izip!(delims, self.path.iter()) {
760
22
                match ent {
761
4
                    PE::ArrayIndex(index) => write!(f, "[{}]", index)?,
762
18
                    PE::MapEntry(s) => {
763
18
                        if ok_unquoted(s) {
764
14
                            write!(f, "{}{}", delim, s)?;
765
                        } else {
766
4
                            write!(f, "{}{:?}", delim, s)?;
767
                        }
768
                    }
769
                }
770
            }
771
        }
772
18
        Ok(())
773
18
    }
774
}
775

            
776
/// Would `s` be OK to use unquoted as a key in a TOML file?
777
42
fn ok_unquoted(s: &str) -> bool {
778
42
    let mut chars = s.chars();
779
42
    if let Some(c) = chars.next() {
780
40
        c.is_ascii_alphanumeric()
781
58
            && chars.all(|c| c == '_' || c == '-' || c.is_ascii_alphanumeric())
782
    } else {
783
2
        false
784
    }
785
42
}
786

            
787
#[cfg(test)]
788
#[allow(unreachable_pub)] // impl_standard_builder wants to make pub fns
789
mod test {
790
    // @@ begin test lint list maintained by maint/add_warning @@
791
    #![allow(clippy::bool_assert_comparison)]
792
    #![allow(clippy::clone_on_copy)]
793
    #![allow(clippy::dbg_macro)]
794
    #![allow(clippy::mixed_attributes_style)]
795
    #![allow(clippy::print_stderr)]
796
    #![allow(clippy::print_stdout)]
797
    #![allow(clippy::single_char_pattern)]
798
    #![allow(clippy::unwrap_used)]
799
    #![allow(clippy::unchecked_time_subtraction)]
800
    #![allow(clippy::useless_vec)]
801
    #![allow(clippy::needless_pass_by_value)]
802
    #![allow(clippy::string_slice)] // See arti#2571
803
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
804
    use super::*;
805
    use crate::*;
806
    use derive_deftly::Deftly;
807

            
808
    fn parse_test_set(l: &[&str]) -> BTreeSet<DisfavouredKey> {
809
        l.iter()
810
            .map(|s| DisfavouredKey {
811
                path: s
812
                    .split('.')
813
                    .map(|s| PathEntry::MapEntry(s.into()))
814
                    .collect_vec(),
815
            })
816
            .collect()
817
    }
818

            
819
    #[test]
820
    #[rustfmt::skip] // preserve the layout so we can match vertically by eye
821
    fn test_intersect_unrecognized_list() {
822
        let chk = |a, b, exp| {
823
            let got = intersect_unrecognized_lists(parse_test_set(a), parse_test_set(b));
824
            let exp = parse_test_set(exp);
825
            assert_eq! { got, exp };
826

            
827
            let got = intersect_unrecognized_lists(parse_test_set(b), parse_test_set(a));
828
            assert_eq! { got, exp };
829
        };
830

            
831
        chk(&[ "a", "b",     ],
832
            &[ "a",      "c" ],
833
            &[ "a" ]);
834

            
835
        chk(&[ "a", "b",      "d" ],
836
            &[ "a",      "c", "d" ],
837
            &[ "a",           "d" ]);
838

            
839
        chk(&[ "x.a", "x.b",     ],
840
            &[ "x.a",      "x.c" ],
841
            &[ "x.a" ]);
842

            
843
        chk(&[ "t", "u", "v",          "w"     ],
844
            &[ "t",      "v.a", "v.b",     "x" ],
845
            &[ "t",      "v.a", "v.b",         ]);
846

            
847
        chk(&[ "t",      "v",              "x" ],
848
            &[ "t", "u", "v.a", "v.b", "w"     ],
849
            &[ "t",      "v.a", "v.b",         ]);
850
    }
851

            
852
    #[test]
853
    #[allow(clippy::bool_assert_comparison)] // much clearer this way IMO
854
    fn test_ok_unquoted() {
855
        assert_eq! { false, ok_unquoted("") };
856
        assert_eq! { false, ok_unquoted("_") };
857
        assert_eq! { false, ok_unquoted(".") };
858
        assert_eq! { false, ok_unquoted("-") };
859
        assert_eq! { false, ok_unquoted("_a") };
860
        assert_eq! { false, ok_unquoted(".a") };
861
        assert_eq! { false, ok_unquoted("-a") };
862
        assert_eq! { false, ok_unquoted("a.") };
863
        assert_eq! { true, ok_unquoted("a") };
864
        assert_eq! { true, ok_unquoted("1") };
865
        assert_eq! { true, ok_unquoted("z") };
866
        assert_eq! { true, ok_unquoted("aa09_-") };
867
    }
868

            
869
    #[test]
870
    fn test_display_key() {
871
        let chk = |exp, path: &[PathEntry]| {
872
            assert_eq! { DisfavouredKey { path: path.into() }.to_string(), exp };
873
        };
874
        let me = |s: &str| PathEntry::MapEntry(s.into());
875
        use PathEntry::ArrayIndex as AI;
876

            
877
        chk(r#""""#, &[]);
878
        chk(r#""@""#, &[me("@")]);
879
        chk(r#""\\""#, &[me(r#"\"#)]);
880
        chk(r#"foo"#, &[me("foo")]);
881
        chk(r#"foo.bar"#, &[me("foo"), me("bar")]);
882
        chk(r#"foo[10]"#, &[me("foo"), AI(10)]);
883
        chk(r#"[10].bar"#, &[AI(10), me("bar")]); // weird
884
    }
885

            
886
    #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
887
    #[derive_deftly(TorConfig)]
888
    struct TestConfigA {
889
        #[deftly(tor_config(default))]
890
        a: String,
891
    }
892
    impl TopLevel for TestConfigA {
893
        type Builder = TestConfigABuilder;
894
    }
895

            
896
    #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
897
    #[derive_deftly(TorConfig)]
898
    struct TestConfigB {
899
        #[deftly(tor_config(default))]
900
        b: String,
901

            
902
        #[deftly(tor_config(default))]
903
        old: bool,
904
    }
905
    impl TopLevel for TestConfigB {
906
        type Builder = TestConfigBBuilder;
907
        const DEPRECATED_KEYS: &'static [&'static str] = &["old"];
908
    }
909

            
910
    #[test]
911
    fn test_resolve() {
912
        let test_data = r#"
913
            wombat = 42
914
            a = "hi"
915
            old = true
916
        "#;
917
        let cfg = {
918
            let mut sources = crate::ConfigurationSources::new_empty();
919
            sources.push_source(
920
                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
921
                crate::sources::MustRead::MustRead,
922
            );
923
            sources.load().unwrap()
924
        };
925

            
926
        let _: (TestConfigA, TestConfigB) = resolve_ignore_warnings(cfg.clone()).unwrap();
927

            
928
        let resolved: ResolutionResults<(TestConfigA, TestConfigB)> =
929
            resolve_return_results(cfg, &Default::default()).unwrap();
930
        let (a, b) = resolved.value;
931

            
932
        let mk_strings =
933
            |l: Vec<DisfavouredKey>| l.into_iter().map(|ik| ik.to_string()).collect_vec();
934

            
935
        let ign = mk_strings(resolved.unrecognized);
936
        let depr = mk_strings(resolved.deprecated);
937

            
938
        assert_eq! { &a, &TestConfigA { a: "hi".into() } };
939
        assert_eq! { &b, &TestConfigB { b: "".into(), old: true } };
940
        assert_eq! { ign, &["wombat"] };
941
        assert_eq! { depr, &["old"] };
942

            
943
        let _ = TestConfigA::builder();
944
        let _ = TestConfigB::builder();
945
    }
946

            
947
    #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
948
    #[derive_deftly(TorConfig)]
949
    struct TestConfigC {
950
        #[deftly(tor_config(default))]
951
        c: u32,
952
    }
953
    impl TopLevel for TestConfigC {
954
        type Builder = TestConfigCBuilder;
955
    }
956

            
957
    #[test]
958
    fn build_error() {
959
        // Make sure that errors are propagated correctly.
960
        let test_data = r#"
961
            # wombat is not a number.
962
            c = "wombat"
963
            # this _would_ be unrecognized, but for the errors.
964
            persimmons = "sweet"
965
        "#;
966
        // suppress a dead-code warning.
967
        let _b = TestConfigC::builder();
968

            
969
        let cfg = {
970
            let mut sources = crate::ConfigurationSources::new_empty();
971
            sources.push_source(
972
                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
973
                crate::sources::MustRead::MustRead,
974
            );
975
            sources.load().unwrap()
976
        };
977

            
978
        {
979
            // First try "A", then "C".
980
            let res1: Result<ResolutionResults<(TestConfigA, TestConfigC)>, _> =
981
                resolve_return_results(cfg.clone(), &Default::default());
982
            assert!(res1.is_err());
983
            assert!(matches!(res1, Err(ConfigResolveError::Deserialize(_))));
984
        }
985
        {
986
            // Now the other order: first try "C", then "A".
987
            let res2: Result<ResolutionResults<(TestConfigC, TestConfigA)>, _> =
988
                resolve_return_results(cfg.clone(), &Default::default());
989
            assert!(res2.is_err());
990
            assert!(matches!(res2, Err(ConfigResolveError::Deserialize(_))));
991
        }
992
        // Try manually, to make sure unrecognized fields are removed.
993
        let mut ctx = ResolveContext {
994
            input: cfg,
995
            unrecognized: UnrecognizedKeys::AllKeys,
996
            output_tree: None,
997
        };
998
        let _res3 = TestConfigA::resolve(&mut ctx);
999
        // After resolving A, some fields are unrecognized.
        assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if !k.is_empty()));
        {
            let res4 = TestConfigC::resolve(&mut ctx);
            assert!(matches!(res4, Err(ConfigResolveError::Deserialize(_))));
        }
        {
            // After resolving C with an error, the unrecognized-field list is cleared.
            assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if k.is_empty()));
        }
    }
}