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 serde::{Deserialize, Serialize};
52
//!
53
//! #[derive(Debug, Clone, Builder, Eq, PartialEq)]
54
//! #[builder(build_fn(error = "ConfigBuildError"))]
55
//! #[builder(derive(Debug, Serialize, Deserialize))]
56
//! struct EmbedderConfig {
57
//!     // ....
58
//! }
59
//! impl_standard_builder! { EmbedderConfig }
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
//! #
71
//! # #[derive(Debug, Clone, Builder, Eq, PartialEq)]
72
//! # #[builder(build_fn(error = "ConfigBuildError"))]
73
//! # #[builder(derive(Debug, Serialize, Deserialize))]
74
//! # struct ArtiConfig { }
75
//! # impl_standard_builder! { ArtiConfig }
76
//! # impl TopLevel for ArtiConfig { type Builder = ArtiConfigBuilder; }
77
//!
78
//! let cfg_sources = ConfigurationSources::new_empty(); // In real program, use from_cmdline
79
//! let cfg = cfg_sources.load()?;
80
//!
81
//! let (tcc, arti_config, embedder_config) =
82
//!      tor_config::resolve::<(TorClientConfig, ArtiConfig, EmbedderConfig)>(cfg)?;
83
//!
84
//! let _: EmbedderConfig = embedder_config; // etc.
85
//!
86
//! # Ok(())
87
//! # }
88
//! ```
89

            
90
use std::collections::BTreeSet;
91
use std::fmt::{self, Display};
92
use std::iter;
93
use std::mem;
94

            
95
use itertools::{Itertools, chain, izip};
96
use serde::de::DeserializeOwned;
97
use thiserror::Error;
98
use tracing::warn;
99

            
100
use crate::{ConfigBuildError, ConfigurationTree};
101

            
102
/// Error resolving a configuration (during deserialize, or build)
103
#[derive(Error, Debug)]
104
#[non_exhaustive]
105
pub enum ConfigResolveError {
106
    /// Deserialize failed
107
    #[error("Config contents not as expected")]
108
    Deserialize(#[from] crate::ConfigError),
109

            
110
    /// Build failed
111
    #[error("Config semantically incorrect")]
112
    Build(#[from] ConfigBuildError),
113
}
114

            
115
/// A type that can be built from a builder.
116
pub trait Buildable {
117
    /// The type that constructs this Buildable.
118
    ///
119
    /// Typically, this type will implement [`Builder`].
120
    /// If it does, then `<Self::Builder>::Built` should be `Self`.
121
    type Builder;
122

            
123
    /// Return a new Builder for this type.
124
    fn builder() -> Self::Builder;
125
}
126

            
127
/// A type that can build some buildable type via a build method.
128
pub trait Builder {
129
    /// The type that this builder constructs.
130
    ///
131
    /// Typically, this type will implement [`Buildable`].
132
    /// If it does, then `<Self::Built as Buildable>::Builder` should be `Self`.
133
    type Built;
134

            
135
    /// Build into a `Built`
136
    ///
137
    /// Often shadows an inherent `build` method
138
    fn build(&self) -> Result<Self::Built, ConfigBuildError>;
139
}
140

            
141
/// Collection of configuration settings that can be deserialized and then built
142
///
143
/// *Do not implement directly.*
144
/// Instead, implement [`TopLevel`]: doing so engages the blanket impl
145
/// for (loosely) `TopLevel + Builder`.
146
///
147
/// Each `Resolvable` corresponds to one or more configuration consumers.
148
///
149
/// Ultimately, one `Resolvable` for all the configuration consumers in an entire
150
/// program will be resolved from a single configuration tree (usually parsed from TOML).
151
///
152
/// Multiple config collections can be resolved from the same configuration,
153
/// via the implementation of `Resolvable` on tuples of `Resolvable`s.
154
/// Use this rather than `#[serde(flatten)]`; the latter prevents useful introspection
155
/// (necessary for reporting unrecognized configuration keys, and testing).
156
///
157
/// (The `resolve` method will be called only from within the `tor_config::load` module.)
158
pub trait Resolvable: Sized {
159
    /// Deserialize and build from a configuration
160
    //
161
    // Implementations must do the following:
162
    //
163
    //  1. Deserializes the input (cloning it to be able to do this)
164
    //     into the `Builder`.
165
    //
166
    //  2. Having used `serde_ignored` to detect unrecognized keys,
167
    //     intersects those with the unrecognized keys recorded in the context.
168
    //
169
    //  3. Calls `build` on the `Builder` to get `Self`.
170
    //
171
    // We provide impls for TopLevels, and tuples of Resolvable.
172
    //
173
    // Cannot be implemented outside this module (except eg as a wrapper or something),
174
    // because that would somehow involve creating `Self` from `ResolveContext`
175
    // but `ResolveContext` is completely opaque outside this module.
176
    fn resolve(input: &mut ResolveContext) -> Result<Self, ConfigResolveError>;
177

            
178
    /// Return a list of deprecated config keys, as "."-separated strings
179
    fn enumerate_deprecated_keys<F>(f: &mut F)
180
    where
181
        F: FnMut(&'static [&'static str]);
182
}
183

            
184
/// Top-level configuration struct, made from a deserializable builder
185
///
186
/// One configuration consumer's configuration settings.
187
///
188
/// Implementing this trait only for top-level configurations,
189
/// which are to be parsed at the root level of a (TOML) config file taxonomy.
190
///
191
/// This trait exists to:
192
///
193
///  * Mark the toplevel configuration structures as suitable for use with [`resolve`]
194
///  * Provide the type of the `Builder` for use by Rust generic code
195
pub trait TopLevel {
196
    /// The `Builder` which can be used to make a `Self`
197
    ///
198
    /// Should satisfy `&'_ Self::Builder: Builder<Built=Self>`
199
    type Builder: DeserializeOwned;
200

            
201
    /// Deprecated config keys, as "."-separates strings
202
    const DEPRECATED_KEYS: &'static [&'static str] = &[];
203
}
204

            
205
/// `impl Resolvable for (A,B..) where A: Resolvable, B: Resolvable ...`
206
///
207
/// The implementation simply calls `Resolvable::resolve` for each output tuple member.
208
///
209
/// `define_for_tuples!{ A B - C D.. }`
210
///
211
/// expands to
212
///  1. `define_for_tuples!{ A B - }`: defines for tuple `(A,B,)`
213
///  2. `define_for_tuples!{ A B C - D.. }`: recurses to generate longer tuples
214
macro_rules! define_for_tuples {
215
    { $( $A:ident )* - $B:ident $( $C:ident )* } => {
216
        define_for_tuples!{ $($A)* - }
217
        define_for_tuples!{ $($A)* $B - $($C)* }
218
    };
219
    { $( $A:ident )* - } => {
220
        impl < $($A,)* > Resolvable for ( $($A,)* )
221
        where $( $A: Resolvable, )*
222
        {
223
342
            fn resolve(cfg: &mut ResolveContext) -> Result<Self, ConfigResolveError> {
224
340
                Ok(( $( $A::resolve(cfg)?, )* ))
225
342
            }
226
342
            fn enumerate_deprecated_keys<NF>(f: &mut NF)
227
342
            where NF: FnMut(&'static [&'static str]) {
228
342
                $( $A::enumerate_deprecated_keys(f); )*
229
342
            }
230
        }
231

            
232
    };
233
}
234
// We could avoid recursion by writing out A B C... several times (in a "triangle") but this
235
// would make it tiresome and error-prone to extend the impl to longer tuples.
236
define_for_tuples! { A - B C D E }
237

            
238
/// Config resolution context, not used outside `tor_config::load`
239
///
240
/// This is public only because it appears in the [`Resolvable`] trait.
241
/// You don't want to try to obtain one.
242
pub struct ResolveContext {
243
    /// The input
244
    input: ConfigurationTree,
245

            
246
    /// Paths unrecognized by all deserializations
247
    ///
248
    /// None means we haven't deserialized anything yet, ie means the universal set.
249
    ///
250
    /// Empty is used to disable this feature.
251
    unrecognized: UnrecognizedKeys,
252
}
253

            
254
/// Keys we have *not* recognized so far
255
///
256
/// Initially `AllKeys`, since we haven't recognized any.
257
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
258
enum UnrecognizedKeys {
259
    /// No keys have yet been recognized, so everything in the config is unrecognized
260
    AllKeys,
261

            
262
    /// The keys which remain unrecognized by any consumer
263
    ///
264
    /// If this is empty, we do not (need to) do any further tracking.
265
    These(BTreeSet<DisfavouredKey>),
266
}
267
use UnrecognizedKeys as UK;
268

            
269
impl UnrecognizedKeys {
270
    /// Does it represent the empty set
271
7924
    fn is_empty(&self) -> bool {
272
7924
        match self {
273
4079
            UK::AllKeys => false,
274
3845
            UK::These(ign) => ign.is_empty(),
275
        }
276
7924
    }
277

            
278
    /// Update in place, intersecting with `other`
279
7560
    fn intersect_with(&mut self, other: BTreeSet<DisfavouredKey>) {
280
7560
        match self {
281
4077
            UK::AllKeys => *self = UK::These(other),
282
3483
            UK::These(self_) => {
283
3483
                let tign = mem::take(self_);
284
3483
                *self_ = intersect_unrecognized_lists(tign, other);
285
3483
            }
286
        }
287
7560
    }
288

            
289
    /// Remove every element of this set.
290
6
    fn clear(&mut self) {
291
6
        *self = UK::These(BTreeSet::new());
292
6
    }
293
}
294

            
295
/// Key in config file(s) which is disfavoured (unrecognized or deprecated)
296
///
297
/// [`Display`]s in an approximation to TOML format.
298
/// You can use the [`to_string()`](ToString::to_string) method to obtain
299
/// a string containing a TOML key path.
300
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
301
pub struct DisfavouredKey {
302
    /// Can be empty only before returned from this module
303
    path: Vec<PathEntry>,
304
}
305

            
306
/// Element of an DisfavouredKey
307
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
308
enum PathEntry {
309
    /// Array index
310
    ///
311
    ArrayIndex(usize),
312
    /// Map entry
313
    ///
314
    /// string value is unquoted, needs quoting for display
315
    MapEntry(String),
316
}
317

            
318
/// Deserialize and build overall configuration from config sources
319
///
320
/// Inner function used by all the `resolve_*` family
321
350
fn resolve_inner<T>(
322
350
    input: ConfigurationTree,
323
350
    want_disfavoured: bool,
324
350
) -> Result<ResolutionResults<T>, ConfigResolveError>
325
350
where
326
350
    T: Resolvable,
327
{
328
350
    let mut deprecated = BTreeSet::new();
329

            
330
350
    if want_disfavoured {
331
688
        T::enumerate_deprecated_keys(&mut |l: &[&str]| {
332
1358
            for key in l {
333
670
                match input.0.find_value(key) {
334
668
                    Err(_) => {}
335
2
                    Ok(_) => {
336
2
                        deprecated.insert(key);
337
2
                    }
338
                }
339
            }
340
688
        });
341
2
    }
342

            
343
350
    let mut lc = ResolveContext {
344
350
        input,
345
350
        unrecognized: if want_disfavoured {
346
348
            UK::AllKeys
347
        } else {
348
2
            UK::These(BTreeSet::new())
349
        },
350
    };
351

            
352
350
    let value = Resolvable::resolve(&mut lc)?;
353

            
354
346
    let unrecognized = match lc.unrecognized {
355
        UK::AllKeys => panic!("all unrecognized, as if we had processed nothing"),
356
346
        UK::These(ign) => ign,
357
    }
358
346
    .into_iter()
359
346
    .filter(|ip| !ip.path.is_empty())
360
346
    .collect_vec();
361

            
362
346
    let deprecated = deprecated
363
346
        .into_iter()
364
346
        .map(|key| {
365
2
            let path = key
366
2
                .split('.')
367
2
                .map(|e| PathEntry::MapEntry(e.into()))
368
2
                .collect_vec();
369
2
            DisfavouredKey { path }
370
2
        })
371
346
        .collect_vec();
372

            
373
346
    Ok(ResolutionResults {
374
346
        value,
375
346
        unrecognized,
376
346
        deprecated,
377
346
    })
378
350
}
379

            
380
/// Deserialize and build overall configuration from config sources
381
///
382
/// Unrecognized config keys are reported as log warning messages.
383
///
384
/// Resolve the whole configuration in one go, using the `Resolvable` impl on `(A,B)`
385
/// if necessary, so that unrecognized config key processing works correctly.
386
///
387
/// This performs step 3 of the overall config processing,
388
/// as described in the [`tor_config` crate-level documentation](crate).
389
///
390
/// For an example, see the
391
/// [`tor_config::load` module-level documentation](self).
392
326
pub fn resolve<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
393
326
where
394
326
    T: Resolvable,
395
{
396
    let ResolutionResults {
397
326
        value,
398
326
        unrecognized,
399
326
        deprecated,
400
326
    } = resolve_inner(input, true)?;
401
326
    for depr in deprecated {
402
        warn!("deprecated configuration key: {}", &depr);
403
    }
404
326
    for ign in unrecognized {
405
        warn!("unrecognized configuration key: {}", &ign);
406
    }
407
326
    Ok(value)
408
326
}
409

            
410
/// Deserialize and build overall configuration, reporting unrecognized keys in the return value
411
22
pub fn resolve_return_results<T>(
412
22
    input: ConfigurationTree,
413
22
) -> Result<ResolutionResults<T>, ConfigResolveError>
414
22
where
415
22
    T: Resolvable,
416
{
417
22
    resolve_inner(input, true)
418
22
}
419

            
420
/// Results of a successful `resolve_return_disfavoured`
421
#[derive(Debug, Clone)]
422
#[non_exhaustive]
423
pub struct ResolutionResults<T> {
424
    /// The configuration, successfully parsed
425
    pub value: T,
426

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

            
430
    /// Any config keys which were found, but have been declared deprecated
431
    pub deprecated: Vec<DisfavouredKey>,
432
}
433

            
434
/// Deserialize and build overall configuration, silently ignoring unrecognized config keys
435
2
pub fn resolve_ignore_warnings<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
436
2
where
437
2
    T: Resolvable,
438
{
439
2
    Ok(resolve_inner(input, false)?.value)
440
2
}
441

            
442
/// Wrapper around T that collects ignored keys as we deserialize it.
443
///
444
/// (We need a helper type here since figment does not expose a `Deserializer`
445
/// implementation directly.)
446
struct Des<T> {
447
    /// A set of the ignored keys that we found
448
    nign: BTreeSet<DisfavouredKey>,
449
    /// The underlying value we're deserializing.
450
    value: T,
451
}
452
impl<'de, T> serde::Deserialize<'de> for Des<T>
453
where
454
    T: serde::Deserialize<'de>,
455
{
456
678
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
457
678
    where
458
678
        D: serde::Deserializer<'de>,
459
    {
460
678
        let mut nign = BTreeSet::new();
461
1676
        let mut recorder = |path: serde_ignored::Path<'_>| {
462
1672
            nign.insert(copy_path(&path));
463
1672
        };
464
678
        let deser = serde_ignored::Deserializer::new(deserializer, &mut recorder);
465
678
        let ret = serde::Deserialize::deserialize(deser);
466
678
        Ok(Des { nign, value: ret? })
467
678
    }
468
}
469

            
470
impl<T> Resolvable for T
471
where
472
    T: TopLevel,
473
    T::Builder: Builder<Built = Self>,
474
{
475
694
    fn resolve(input: &mut ResolveContext) -> Result<T, ConfigResolveError> {
476
694
        let deser = input.input.clone();
477
694
        let builder: Result<T::Builder, _> = {
478
            // If input.unrecognized.is_empty() then we don't bother tracking the
479
            // unrecognized keys since we would intersect with the empty set.
480
            // That is how this tracking is disabled when we want it to be.
481
694
            let want_unrecognized = !input.unrecognized.is_empty();
482
694
            if !want_unrecognized {
483
16
                deser.0.extract_lossy()
484
            } else {
485
678
                let ret: Result<Des<<T as TopLevel>::Builder>, _> = deser.0.extract_lossy();
486

            
487
678
                match ret {
488
672
                    Ok(Des { nign, value }) => {
489
672
                        input.unrecognized.intersect_with(nign);
490
672
                        Ok(value)
491
                    }
492
6
                    Err(e) => {
493
                        // If we got an error, the config might only have been partially processed,
494
                        // so we might get false positives.  Disable the unrecognized tracking.
495
6
                        input.unrecognized.clear();
496
6
                        Err(e)
497
                    }
498
                }
499
            }
500
        };
501
694
        let built = builder.map_err(crate::ConfigError::from_cfg_err)?.build()?;
502
688
        Ok(built)
503
694
    }
504

            
505
692
    fn enumerate_deprecated_keys<NF>(f: &mut NF)
506
692
    where
507
692
        NF: FnMut(&'static [&'static str]),
508
    {
509
692
        f(T::DEPRECATED_KEYS);
510
692
    }
511
}
512

            
513
/// Turns a [`serde_ignored::Path`] (which is borrowed) into an owned `DisfavouredKey`
514
19250
fn copy_path(mut path: &serde_ignored::Path) -> DisfavouredKey {
515
    use PathEntry as PE;
516
    use serde_ignored::Path as SiP;
517

            
518
19250
    let mut descend = vec![];
519
    loop {
520
46524
        let (new_path, ent) = match path {
521
19250
            SiP::Root => break,
522
            SiP::Seq { parent, index } => (parent, Some(PE::ArrayIndex(*index))),
523
27274
            SiP::Map { parent, key } => (parent, Some(PE::MapEntry(key.clone()))),
524
            SiP::Some { parent }
525
            | SiP::NewtypeStruct { parent }
526
            | SiP::NewtypeVariant { parent } => (parent, None),
527
        };
528
27274
        descend.extend(ent);
529
27274
        path = new_path;
530
    }
531
19250
    descend.reverse();
532
19250
    DisfavouredKey { path: descend }
533
19250
}
534

            
535
/// Computes the intersection, resolving ignorances at different depths
536
///
537
/// Eg if `a` contains `application.wombat` and `b` contains `application`,
538
/// we need to return `application.wombat`.
539
///
540
/// # Formally
541
///
542
/// A configuration key (henceforth "key") is a sequence of `PathEntry`,
543
/// interpreted as denoting a place in a tree-like hierarchy.
544
///
545
/// Each input `BTreeSet` denotes a subset of the configuration key space.
546
/// Any key in the set denotes itself, but also all possible keys which have it as a prefix.
547
/// We say a s set is "minimal" if it doesn't have entries made redundant by this rule.
548
///
549
/// This function computes a minimal intersection of two minimal inputs.
550
/// If the inputs are not minimal, the output may not be either
551
/// (although `serde_ignored` gives us minimal sets, so that case is not important).
552
3503
fn intersect_unrecognized_lists(
553
3503
    al: BTreeSet<DisfavouredKey>,
554
3503
    bl: BTreeSet<DisfavouredKey>,
555
3503
) -> BTreeSet<DisfavouredKey> {
556
    //eprintln!("INTERSECT:");
557
    //for ai in &al { eprintln!("A: {}", ai); }
558
    //for bi in &bl { eprintln!("B: {}", bi); }
559

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

            
565
7076
    let mut inputs: [_; 2] = [al, bl].map(|input| input.into_iter().peekable());
566
3503
    let mut output = BTreeSet::new();
567

            
568
    // The BTreeSets produce items in sort order.
569
    //
570
    // We maintain the following invariants (valid at the top of the loop):
571
    //
572
    //   For every possible key *strictly earlier* than those remaining in either input,
573
    //   the output contains the key iff it was in the intersection.
574
    //
575
    //   No other keys appear in the output.
576
    //
577
    // We peek at the next two items.  The possible cases are:
578
    //
579
    //   0. One or both inputs is used up.  In that case none of any remaining input
580
    //      can be in the intersection and we are done.
581
    //
582
    //   1. The two inputs have the same next item.  In that case the item is in the
583
    //      intersection.  If the inputs are minimal, no children of that item can appear
584
    //      in either input, so we can make our own output minimal without thinking any
585
    //      more about this item from the point of view of either list.
586
    //
587
    //   2. One of the inputs is a prefix of the other.  In this case the longer item is
588
    //      in the intersection - as are all subsequent items from the same input which
589
    //      also share that prefix.  Then, we must discard the shorter item (which denotes
590
    //      the whole subspace of which only part is in the intersection).
591
    //
592
    //   3. Otherwise, the earlier item is definitely not in the intersection and
593
    //      we can munch it.
594

            
595
    // Peek one from each, while we can.
596
11123
    while let Ok(items) = {
597
        // Ideally we would use array::try_map but it's nightly-only
598
14626
        <[_; 2]>::try_from(
599
14626
            inputs
600
14626
                .iter_mut()
601
29554
                .flat_map(|input: &'_ mut _| input.peek()) // keep the Somes
602
14626
                .collect::<Vec<_>>(), // if we had 2 Somes we can make a [_; 2] from this
603
        )
604
    } {
605
22478
        let shorter_len = items.iter().map(|i| i.path.len()).min().expect("wrong #");
606
11123
        let earlier_i = items
607
11123
            .iter()
608
11123
            .enumerate()
609
11123
            .min_by_key(|&(_i, item)| *item)
610
11123
            .expect("wrong #")
611
            .0;
612
11123
        let later_i = 1 - earlier_i;
613

            
614
11123
        if items.iter().all_equal() {
615
            // Case 0. above.
616
            //
617
            // Take the identical items off the front of both iters,
618
            // and put one into the output (the last will do nicely).
619
            //dbg!(items);
620
26
            let item = inputs
621
26
                .iter_mut()
622
39
                .map(|input| input.next().expect("but peeked"))
623
26
                .next_back()
624
26
                .expect("wrong #");
625
26
            output.insert(item);
626
26
            continue;
627
11097
        } else if items
628
11097
            .iter()
629
22413
            .map(|item| &item.path[0..shorter_len])
630
11097
            .all_equal()
631
        {
632
            // Case 2.  One is a prefix of the other.   earlier_i is the shorter one.
633
8
            let shorter_item = items[earlier_i];
634
8
            let prefix = shorter_item.path.clone(); // borrowck can't prove disjointness
635

            
636
            // Keep copying items from the side with the longer entries,
637
            // so long as they fall within (have the prefix of) the shorter entry.
638
            //dbg!(items, shorter_item, &prefix);
639
24
            while let Some(longer_item) = inputs[later_i].peek() {
640
24
                if !longer_item.path.starts_with(&prefix) {
641
8
                    break;
642
16
                }
643
16
                let longer_item = inputs[later_i].next().expect("but peeked");
644
16
                output.insert(longer_item);
645
            }
646
            // We've "used up" the shorter item.
647
8
            let _ = inputs[earlier_i].next().expect("but peeked");
648
11089
        } else {
649
11089
            // Case 3.  The items are just different.  Eat the earlier one.
650
11089
            //dbg!(items, earlier_i);
651
11089
            let _ = inputs[earlier_i].next().expect("but peeked");
652
11089
        }
653
    }
654
    // Case 0.  At least one of the lists is empty, giving Err() from the array
655

            
656
    //for oi in &ol { eprintln!("O: {}", oi); }
657
3503
    output
658
3503
}
659

            
660
impl Display for DisfavouredKey {
661
18
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
662
        use PathEntry as PE;
663
18
        if self.path.is_empty() {
664
            // shouldn't happen with calls outside this module, and shouldn't be used inside
665
            // but handle it anyway
666
2
            write!(f, r#""""#)?;
667
        } else {
668
16
            let delims = chain!(iter::once(""), iter::repeat("."));
669
22
            for (delim, ent) in izip!(delims, self.path.iter()) {
670
22
                match ent {
671
4
                    PE::ArrayIndex(index) => write!(f, "[{}]", index)?,
672
18
                    PE::MapEntry(s) => {
673
18
                        if ok_unquoted(s) {
674
14
                            write!(f, "{}{}", delim, s)?;
675
                        } else {
676
4
                            write!(f, "{}{:?}", delim, s)?;
677
                        }
678
                    }
679
                }
680
            }
681
        }
682
18
        Ok(())
683
18
    }
684
}
685

            
686
/// Would `s` be OK to use unquoted as a key in a TOML file?
687
42
fn ok_unquoted(s: &str) -> bool {
688
42
    let mut chars = s.chars();
689
42
    if let Some(c) = chars.next() {
690
40
        c.is_ascii_alphanumeric()
691
58
            && chars.all(|c| c == '_' || c == '-' || c.is_ascii_alphanumeric())
692
    } else {
693
2
        false
694
    }
695
42
}
696

            
697
#[cfg(test)]
698
#[allow(unreachable_pub)] // impl_standard_builder wants to make pub fns
699
mod test {
700
    // @@ begin test lint list maintained by maint/add_warning @@
701
    #![allow(clippy::bool_assert_comparison)]
702
    #![allow(clippy::clone_on_copy)]
703
    #![allow(clippy::dbg_macro)]
704
    #![allow(clippy::mixed_attributes_style)]
705
    #![allow(clippy::print_stderr)]
706
    #![allow(clippy::print_stdout)]
707
    #![allow(clippy::single_char_pattern)]
708
    #![allow(clippy::unwrap_used)]
709
    #![allow(clippy::unchecked_time_subtraction)]
710
    #![allow(clippy::useless_vec)]
711
    #![allow(clippy::needless_pass_by_value)]
712
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
713
    use super::*;
714
    use crate::*;
715
    use derive_builder::Builder;
716
    use serde::{Deserialize, Serialize};
717

            
718
    fn parse_test_set(l: &[&str]) -> BTreeSet<DisfavouredKey> {
719
        l.iter()
720
            .map(|s| DisfavouredKey {
721
                path: s
722
                    .split('.')
723
                    .map(|s| PathEntry::MapEntry(s.into()))
724
                    .collect_vec(),
725
            })
726
            .collect()
727
    }
728

            
729
    #[test]
730
    #[rustfmt::skip] // preserve the layout so we can match vertically by eye
731
    fn test_intersect_unrecognized_list() {
732
        let chk = |a, b, exp| {
733
            let got = intersect_unrecognized_lists(parse_test_set(a), parse_test_set(b));
734
            let exp = parse_test_set(exp);
735
            assert_eq! { got, exp };
736

            
737
            let got = intersect_unrecognized_lists(parse_test_set(b), parse_test_set(a));
738
            assert_eq! { got, exp };
739
        };
740

            
741
        chk(&[ "a", "b",     ],
742
            &[ "a",      "c" ],
743
            &[ "a" ]);
744

            
745
        chk(&[ "a", "b",      "d" ],
746
            &[ "a",      "c", "d" ],
747
            &[ "a",           "d" ]);
748

            
749
        chk(&[ "x.a", "x.b",     ],
750
            &[ "x.a",      "x.c" ],
751
            &[ "x.a" ]);
752

            
753
        chk(&[ "t", "u", "v",          "w"     ],
754
            &[ "t",      "v.a", "v.b",     "x" ],
755
            &[ "t",      "v.a", "v.b",         ]);
756

            
757
        chk(&[ "t",      "v",              "x" ],
758
            &[ "t", "u", "v.a", "v.b", "w"     ],
759
            &[ "t",      "v.a", "v.b",         ]);
760
    }
761

            
762
    #[test]
763
    #[allow(clippy::bool_assert_comparison)] // much clearer this way IMO
764
    fn test_ok_unquoted() {
765
        assert_eq! { false, ok_unquoted("") };
766
        assert_eq! { false, ok_unquoted("_") };
767
        assert_eq! { false, ok_unquoted(".") };
768
        assert_eq! { false, ok_unquoted("-") };
769
        assert_eq! { false, ok_unquoted("_a") };
770
        assert_eq! { false, ok_unquoted(".a") };
771
        assert_eq! { false, ok_unquoted("-a") };
772
        assert_eq! { false, ok_unquoted("a.") };
773
        assert_eq! { true, ok_unquoted("a") };
774
        assert_eq! { true, ok_unquoted("1") };
775
        assert_eq! { true, ok_unquoted("z") };
776
        assert_eq! { true, ok_unquoted("aa09_-") };
777
    }
778

            
779
    #[test]
780
    fn test_display_key() {
781
        let chk = |exp, path: &[PathEntry]| {
782
            assert_eq! { DisfavouredKey { path: path.into() }.to_string(), exp };
783
        };
784
        let me = |s: &str| PathEntry::MapEntry(s.into());
785
        use PathEntry::ArrayIndex as AI;
786

            
787
        chk(r#""""#, &[]);
788
        chk(r#""@""#, &[me("@")]);
789
        chk(r#""\\""#, &[me(r#"\"#)]);
790
        chk(r#"foo"#, &[me("foo")]);
791
        chk(r#"foo.bar"#, &[me("foo"), me("bar")]);
792
        chk(r#"foo[10]"#, &[me("foo"), AI(10)]);
793
        chk(r#"[10].bar"#, &[AI(10), me("bar")]); // weird
794
    }
795

            
796
    #[derive(Debug, Clone, Builder, Eq, PartialEq)]
797
    #[builder(build_fn(error = "ConfigBuildError"))]
798
    #[builder(derive(Debug, Serialize, Deserialize))]
799
    struct TestConfigA {
800
        #[builder(default)]
801
        a: String,
802
    }
803
    impl_standard_builder! { TestConfigA }
804
    impl TopLevel for TestConfigA {
805
        type Builder = TestConfigABuilder;
806
    }
807

            
808
    #[derive(Debug, Clone, Builder, Eq, PartialEq)]
809
    #[builder(build_fn(error = "ConfigBuildError"))]
810
    #[builder(derive(Debug, Serialize, Deserialize))]
811
    struct TestConfigB {
812
        #[builder(default)]
813
        b: String,
814

            
815
        #[builder(default)]
816
        old: bool,
817
    }
818
    impl_standard_builder! { TestConfigB }
819
    impl TopLevel for TestConfigB {
820
        type Builder = TestConfigBBuilder;
821
        const DEPRECATED_KEYS: &'static [&'static str] = &["old"];
822
    }
823

            
824
    #[test]
825
    fn test_resolve() {
826
        let test_data = r#"
827
            wombat = 42
828
            a = "hi"
829
            old = true
830
        "#;
831
        let cfg = {
832
            let mut sources = crate::ConfigurationSources::new_empty();
833
            sources.push_source(
834
                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
835
                crate::sources::MustRead::MustRead,
836
            );
837
            sources.load().unwrap()
838
        };
839

            
840
        let _: (TestConfigA, TestConfigB) = resolve_ignore_warnings(cfg.clone()).unwrap();
841

            
842
        let resolved: ResolutionResults<(TestConfigA, TestConfigB)> =
843
            resolve_return_results(cfg).unwrap();
844
        let (a, b) = resolved.value;
845

            
846
        let mk_strings =
847
            |l: Vec<DisfavouredKey>| l.into_iter().map(|ik| ik.to_string()).collect_vec();
848

            
849
        let ign = mk_strings(resolved.unrecognized);
850
        let depr = mk_strings(resolved.deprecated);
851

            
852
        assert_eq! { &a, &TestConfigA { a: "hi".into() } };
853
        assert_eq! { &b, &TestConfigB { b: "".into(), old: true } };
854
        assert_eq! { ign, &["wombat"] };
855
        assert_eq! { depr, &["old"] };
856

            
857
        let _ = TestConfigA::builder();
858
        let _ = TestConfigB::builder();
859
    }
860

            
861
    #[derive(Debug, Clone, Builder, Eq, PartialEq)]
862
    #[builder(build_fn(error = "ConfigBuildError"))]
863
    #[builder(derive(Debug, Serialize, Deserialize))]
864
    struct TestConfigC {
865
        #[builder(default)]
866
        c: u32,
867
    }
868
    impl_standard_builder! { TestConfigC }
869
    impl TopLevel for TestConfigC {
870
        type Builder = TestConfigCBuilder;
871
    }
872

            
873
    #[test]
874
    fn build_error() {
875
        // Make sure that errors are propagated correctly.
876
        let test_data = r#"
877
            # wombat is not a number.
878
            c = "wombat"
879
            # this _would_ be unrecognized, but for the errors.
880
            persimmons = "sweet"
881
        "#;
882
        // suppress a dead-code warning.
883
        let _b = TestConfigC::builder();
884

            
885
        let cfg = {
886
            let mut sources = crate::ConfigurationSources::new_empty();
887
            sources.push_source(
888
                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
889
                crate::sources::MustRead::MustRead,
890
            );
891
            sources.load().unwrap()
892
        };
893

            
894
        {
895
            // First try "A", then "C".
896
            let res1: Result<ResolutionResults<(TestConfigA, TestConfigC)>, _> =
897
                resolve_return_results(cfg.clone());
898
            assert!(res1.is_err());
899
            assert!(matches!(res1, Err(ConfigResolveError::Deserialize(_))));
900
        }
901
        {
902
            // Now the other order: first try "C", then "A".
903
            let res2: Result<ResolutionResults<(TestConfigC, TestConfigA)>, _> =
904
                resolve_return_results(cfg.clone());
905
            assert!(res2.is_err());
906
            assert!(matches!(res2, Err(ConfigResolveError::Deserialize(_))));
907
        }
908
        // Try manually, to make sure unrecognized fields are removed.
909
        let mut ctx = ResolveContext {
910
            input: cfg,
911
            unrecognized: UnrecognizedKeys::AllKeys,
912
        };
913
        let _res3 = TestConfigA::resolve(&mut ctx);
914
        // After resolving A, some fields are unrecognized.
915
        assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if !k.is_empty()));
916
        {
917
            let res4 = TestConfigC::resolve(&mut ctx);
918
            assert!(matches!(res4, Err(ConfigResolveError::Deserialize(_))));
919
        }
920
        {
921
            // After resolving C with an error, the unrecognized-field list is cleared.
922
            assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if k.is_empty()));
923
        }
924
    }
925
}