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 `ConfigSources`, to avoid clutter in the example)
36
//!  * Reads those sources into a single configuration taxonomy [`ConfigurationTree`].
37
//!  * Processes that configuration into a 3-tuple of configuration
38
//!    structs for the three components, namely:
39
//!      - `TorClientConfig`, the configuration for the `arti_client` crate's `TorClient`
40
//!      - `ArtiConfig`, for behaviours in the `arti` command line utility
41
//!      - `EmbedderConfig`.
42
//!  * Will report a warning to the user about config settings found in the config files,
43
//!    but not recognized by *any* of the three config consumers,
44
//!
45
//! ```
46
//! # fn main() -> Result<(), tor_config::load::ConfigResolveError> {
47
//! use derive_builder::Builder;
48
//! use tor_config::{impl_standard_builder, resolve, ConfigBuildError, ConfigurationSources};
49
//! use tor_config::load::TopLevel;
50
//! use serde::{Deserialize, Serialize};
51
//!
52
//! #[derive(Debug, Clone, Builder, Eq, PartialEq)]
53
//! #[builder(build_fn(error = "ConfigBuildError"))]
54
//! #[builder(derive(Debug, Serialize, Deserialize))]
55
//! struct EmbedderConfig {
56
//!     // ....
57
//! }
58
//! impl_standard_builder! { EmbedderConfig }
59
//! impl TopLevel for EmbedderConfig {
60
//!     type Builder = EmbedderConfigBuilder;
61
//! }
62
//! #
63
//! # #[derive(Debug, Clone, Builder, Eq, PartialEq)]
64
//! # #[builder(build_fn(error = "ConfigBuildError"))]
65
//! # #[builder(derive(Debug, Serialize, Deserialize))]
66
//! # struct TorClientConfig { }
67
//! # impl_standard_builder! { TorClientConfig }
68
//! # impl TopLevel for TorClientConfig { type Builder = TorClientConfigBuilder; }
69
//! #
70
//! # #[derive(Debug, Clone, Builder, Eq, PartialEq)]
71
//! # #[builder(build_fn(error = "ConfigBuildError"))]
72
//! # #[builder(derive(Debug, Serialize, Deserialize))]
73
//! # struct ArtiConfig { }
74
//! # impl_standard_builder! { ArtiConfig }
75
//! # impl TopLevel for ArtiConfig { type Builder = ArtiConfigBuilder; }
76
//!
77
//! let cfg_sources = ConfigurationSources::new_empty(); // In real program, use from_cmdline
78
//! let cfg = cfg_sources.load()?;
79
//!
80
//! let (tcc, arti_config, embedder_config) =
81
//!      tor_config::resolve::<(TorClientConfig, ArtiConfig, EmbedderConfig)>(cfg)?;
82
//!
83
//! let _: EmbedderConfig = embedder_config; // etc.
84
//!
85
//! # Ok(())
86
//! # }
87
//! ```
88

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

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

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

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

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

            
114
/// A type that can be built from a builder via a build method
115
pub trait Builder {
116
    /// The type that this builder constructs
117
    type Built;
118
    /// Build into a `Built`
119
    ///
120
    /// Often shadows an inherent `build` method
121
    fn build(&self) -> Result<Self::Built, ConfigBuildError>;
122
}
123

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

            
161
    /// Return a list of deprecated config keys, as "."-separated strings
162
    fn enumerate_deprecated_keys<F>(f: &mut F)
163
    where
164
        F: FnMut(&'static [&'static str]);
165
}
166

            
167
/// Top-level configuration struct, made from a deserializable builder
168
///
169
/// One configuration consumer's configuration settings.
170
///
171
/// Implementing this trait only for top-level configurations,
172
/// which are to be parsed at the root level of a (TOML) config file taxonomy.
173
///
174
/// This trait exists to:
175
///
176
///  * Mark the toplevel configuration structures as suitable for use with [`resolve`]
177
///  * Provide the type of the `Builder` for use by Rust generic code
178
pub trait TopLevel {
179
    /// The `Builder` which can be used to make a `Self`
180
    ///
181
    /// Should satisfy `&'_ Self::Builder: Builder<Built=Self>`
182
    type Builder: DeserializeOwned;
183

            
184
    /// Deprecated config keys, as "."-separates strings
185
    const DEPRECATED_KEYS: &'static [&'static str] = &[];
186
}
187

            
188
/// `impl Resolvable for (A,B..) where A: Resolvable, B: Resolvable ...`
189
///
190
/// The implementation simply calls `Resolvable::resolve` for each output tuple member.
191
///
192
/// `define_for_tuples!{ A B - C D.. }`
193
///
194
/// expands to
195
///  1. `define_for_tuples!{ A B - }`: defines for tuple `(A,B,)`
196
///  2. `define_for_tuples!{ A B C - D.. }`: recurses to generate longer tuples
197
macro_rules! define_for_tuples {
198
    { $( $A:ident )* - $B:ident $( $C:ident )* } => {
199
        define_for_tuples!{ $($A)* - }
200
        define_for_tuples!{ $($A)* $B - $($C)* }
201
    };
202
    { $( $A:ident )* - } => {
203
        impl < $($A,)* > Resolvable for ( $($A,)* )
204
        where $( $A: Resolvable, )*
205
        {
206
64
            fn resolve(cfg: &mut ResolveContext) -> Result<Self, ConfigResolveError> {
207
62
                Ok(( $( $A::resolve(cfg)?, )* ))
208
64
            }
209
64
            fn enumerate_deprecated_keys<NF>(f: &mut NF)
210
64
            where NF: FnMut(&'static [&'static str]) {
211
64
                $( $A::enumerate_deprecated_keys(f); )*
212
64
            }
213
        }
214

            
215
    };
216
}
217
// We could avoid recursion by writing out A B C... several times (in a "triangle") but this
218
// would make it tiresome and error-prone to extend the impl to longer tuples.
219
define_for_tuples! { A - B C D E }
220

            
221
/// Config resolution context, not used outside `tor_config::load`
222
///
223
/// This is public only because it appears in the [`Resolvable`] trait.
224
/// You don't want to try to obtain one.
225
pub struct ResolveContext {
226
    /// The input
227
    input: ConfigurationTree,
228

            
229
    /// Paths unrecognized by all deserializations
230
    ///
231
    /// None means we haven't deserialized anything yet, ie means the universal set.
232
    ///
233
    /// Empty is used to disable this feature.
234
    unrecognized: UnrecognizedKeys,
235
}
236

            
237
/// Keys we have *not* recognized so far
238
///
239
/// Initially `AllKeys`, since we haven't recognized any.
240
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
241
enum UnrecognizedKeys {
242
    /// No keys have yet been recognized, so everything in the config is unrecognized
243
    AllKeys,
244

            
245
    /// The keys which remain unrecognized by any consumer
246
    ///
247
    /// If this is empty, we do not (need to) do any further tracking.
248
    These(BTreeSet<DisfavouredKey>),
249
}
250
use UnrecognizedKeys as UK;
251

            
252
impl UnrecognizedKeys {
253
    /// Does it represent the empty set
254
2738
    fn is_empty(&self) -> bool {
255
2738
        match self {
256
1504
            UK::AllKeys => false,
257
1234
            UK::These(ign) => ign.is_empty(),
258
        }
259
2738
    }
260

            
261
    /// Update in place, intersecting with `other`
262
2184
    fn intersect_with(&mut self, other: BTreeSet<DisfavouredKey>) {
263
2184
        match self {
264
1502
            UK::AllKeys => *self = UK::These(other),
265
682
            UK::These(self_) => {
266
682
                let tign = mem::take(self_);
267
682
                *self_ = intersect_unrecognized_lists(tign, other);
268
682
            }
269
        }
270
2184
    }
271

            
272
    /// Remove every element of this set.
273
6
    fn clear(&mut self) {
274
6
        *self = UK::These(BTreeSet::new());
275
6
    }
276
}
277

            
278
/// Key in config file(s) which is disfavoured (unrecognized or deprecated)
279
///
280
/// [`Display`]s in an approximation to TOML format.
281
/// You can use the [`to_string()`](ToString::to_string) method to obtain
282
/// a string containing a TOML key path.
283
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
284
pub struct DisfavouredKey {
285
    /// Can be empty only before returned from this module
286
    path: Vec<PathEntry>,
287
}
288

            
289
/// Element of an DisfavouredKey
290
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
291
enum PathEntry {
292
    /// Array index
293
    ///
294
    ArrayIndex(usize),
295
    /// Map entry
296
    ///
297
    /// string value is unquoted, needs quoting for display
298
    MapEntry(String),
299
}
300

            
301
/// Deserialize and build overall configuration from config sources
302
///
303
/// Inner function used by all the `resolve_*` family
304
72
fn resolve_inner<T>(
305
72
    input: ConfigurationTree,
306
72
    want_disfavoured: bool,
307
72
) -> Result<ResolutionResults<T>, ConfigResolveError>
308
72
where
309
72
    T: Resolvable,
310
72
{
311
72
    let mut deprecated = BTreeSet::new();
312
72

            
313
72
    if want_disfavoured {
314
132
        T::enumerate_deprecated_keys(&mut |l: &[&str]| {
315
246
            for key in l {
316
114
                match input.0.find_value(key) {
317
108
                    Err(_) => {}
318
6
                    Ok(_) => {
319
6
                        deprecated.insert(key);
320
6
                    }
321
                }
322
            }
323
132
        });
324
70
    }
325

            
326
72
    let mut lc = ResolveContext {
327
72
        input,
328
72
        unrecognized: if want_disfavoured {
329
70
            UK::AllKeys
330
        } else {
331
2
            UK::These(BTreeSet::new())
332
        },
333
    };
334

            
335
72
    let value = Resolvable::resolve(&mut lc)?;
336

            
337
68
    let unrecognized = match lc.unrecognized {
338
        UK::AllKeys => panic!("all unrecognized, as if we had processed nothing"),
339
68
        UK::These(ign) => ign,
340
68
    }
341
68
    .into_iter()
342
68
    .filter(|ip| !ip.path.is_empty())
343
68
    .collect_vec();
344
68

            
345
68
    let deprecated = deprecated
346
68
        .into_iter()
347
68
        .map(|key| {
348
6
            let path = key
349
6
                .split('.')
350
10
                .map(|e| PathEntry::MapEntry(e.into()))
351
6
                .collect_vec();
352
6
            DisfavouredKey { path }
353
68
        })
354
68
        .collect_vec();
355
68

            
356
68
    Ok(ResolutionResults {
357
68
        value,
358
68
        unrecognized,
359
68
        deprecated,
360
68
    })
361
72
}
362

            
363
/// Deserialize and build overall configuration from config sources
364
///
365
/// Unrecognized config keys are reported as log warning messages.
366
///
367
/// Resolve the whole configuration in one go, using the `Resolvable` impl on `(A,B)`
368
/// if necessary, so that unrecognized config key processing works correctly.
369
///
370
/// This performs step 3 of the overall config processing,
371
/// as described in the [`tor_config` crate-level documentation](crate).
372
///
373
/// For an example, see the
374
/// [`tor_config::load` module-level documentation](self).
375
54
pub fn resolve<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
376
54
where
377
54
    T: Resolvable,
378
54
{
379
    let ResolutionResults {
380
54
        value,
381
54
        unrecognized,
382
54
        deprecated,
383
54
    } = resolve_inner(input, true)?;
384
54
    for depr in deprecated {
385
        warn!("deprecated configuration key: {}", &depr);
386
    }
387
54
    for ign in unrecognized {
388
        warn!("unrecognized configuration key: {}", &ign);
389
    }
390
54
    Ok(value)
391
54
}
392

            
393
/// Deserialize and build overall configuration, reporting unrecognized keys in the return value
394
16
pub fn resolve_return_results<T>(
395
16
    input: ConfigurationTree,
396
16
) -> Result<ResolutionResults<T>, ConfigResolveError>
397
16
where
398
16
    T: Resolvable,
399
16
{
400
16
    resolve_inner(input, true)
401
16
}
402

            
403
/// Results of a successful `resolve_return_disfavoured`
404
#[derive(Debug, Clone)]
405
#[non_exhaustive]
406
pub struct ResolutionResults<T> {
407
    /// The configuration, successfully parsed
408
    pub value: T,
409

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

            
413
    /// Any config keys which were found, but have been declared deprecated
414
    pub deprecated: Vec<DisfavouredKey>,
415
}
416

            
417
/// Deserialize and build overall configuration, silently ignoring unrecognized config keys
418
2
pub fn resolve_ignore_warnings<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
419
2
where
420
2
    T: Resolvable,
421
2
{
422
2
    Ok(resolve_inner(input, false)?.value)
423
2
}
424

            
425
/// Wrapper around T that collects ignored keys as we deserialize it.
426
///
427
/// (We need a helper type here since figment does not expose a `Deserializer`
428
/// implementation directly.)
429
struct Des<T> {
430
    /// A set of the ignored keys that we found
431
    nign: BTreeSet<DisfavouredKey>,
432
    /// The underlying value we're deserializing.
433
    value: T,
434
}
435
impl<'de, T> serde::Deserialize<'de> for Des<T>
436
where
437
    T: serde::Deserialize<'de>,
438
{
439
118
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
440
118
    where
441
118
        D: serde::Deserializer<'de>,
442
118
    {
443
118
        let mut nign = BTreeSet::new();
444
254
        let mut recorder = |path: serde_ignored::Path<'_>| {
445
250
            nign.insert(copy_path(&path));
446
250
        };
447
118
        let deser = serde_ignored::Deserializer::new(deserializer, &mut recorder);
448
118
        let ret = serde::Deserialize::deserialize(deser);
449
118
        Ok(Des { nign, value: ret? })
450
118
    }
451
}
452

            
453
impl<T> Resolvable for T
454
where
455
    T: TopLevel,
456
    T::Builder: Builder<Built = Self>,
457
{
458
138
    fn resolve(input: &mut ResolveContext) -> Result<T, ConfigResolveError> {
459
138
        let deser = input.input.clone();
460
138
        let builder: Result<T::Builder, _> = {
461
            // If input.unrecognized.is_empty() then we don't bother tracking the
462
            // unrecognized keys since we would intersect with the empty set.
463
            // That is how this tracking is disabled when we want it to be.
464
138
            let want_unrecognized = !input.unrecognized.is_empty();
465
138
            if !want_unrecognized {
466
20
                deser.0.extract_lossy()
467
            } else {
468
118
                let ret: Result<Des<<T as TopLevel>::Builder>, _> = deser.0.extract_lossy();
469
118

            
470
118
                match ret {
471
112
                    Ok(Des { nign, value }) => {
472
112
                        input.unrecognized.intersect_with(nign);
473
112
                        Ok(value)
474
                    }
475
6
                    Err(e) => {
476
6
                        // If we got an error, the config might only have been partially processed,
477
6
                        // so we might get false positives.  Disable the unrecognized tracking.
478
6
                        input.unrecognized.clear();
479
6
                        Err(e)
480
                    }
481
                }
482
            }
483
        };
484
138
        let built = builder.map_err(crate::ConfigError::from_cfg_err)?.build()?;
485
132
        Ok(built)
486
138
    }
487

            
488
136
    fn enumerate_deprecated_keys<NF>(f: &mut NF)
489
136
    where
490
136
        NF: FnMut(&'static [&'static str]),
491
136
    {
492
136
        f(T::DEPRECATED_KEYS);
493
136
    }
494
}
495

            
496
/// Turns a [`serde_ignored::Path`] (which is borrowed) into an owned `DisfavouredKey`
497
5524
fn copy_path(mut path: &serde_ignored::Path) -> DisfavouredKey {
498
    use serde_ignored::Path as SiP;
499
    use PathEntry as PE;
500

            
501
5524
    let mut descend = vec![];
502
    loop {
503
11184
        let (new_path, ent) = match path {
504
5524
            SiP::Root => break,
505
            SiP::Seq { parent, index } => (parent, Some(PE::ArrayIndex(*index))),
506
5660
            SiP::Map { parent, key } => (parent, Some(PE::MapEntry(key.clone()))),
507
            SiP::Some { parent }
508
            | SiP::NewtypeStruct { parent }
509
            | SiP::NewtypeVariant { parent } => (parent, None),
510
        };
511
5660
        descend.extend(ent);
512
5660
        path = new_path;
513
    }
514
5524
    descend.reverse();
515
5524
    DisfavouredKey { path: descend }
516
5524
}
517

            
518
/// Computes the intersection, resolving ignorances at different depths
519
///
520
/// Eg if `a` contains `application.wombat` and `b` contains `application`,
521
/// we need to return `application.wombat`.
522
///
523
/// # Formally
524
///
525
/// A configuration key (henceforth "key") is a sequence of `PathEntry`,
526
/// interpreted as denoting a place in a tree-like hierarchy.
527
///
528
/// Each input `BTreeSet` denotes a subset of the configuration key space.
529
/// Any key in the set denotes itself, but also all possible keys which have it as a prefix.
530
/// We say a s set is "minimal" if it doesn't have entries made redundant by this rule.
531
///
532
/// This function computes a minimal intersection of two minimal inputs.
533
/// If the inputs are not minimal, the output may not be either
534
/// (although `serde_ignored` gives us minimal sets, so that case is not important).
535
702
fn intersect_unrecognized_lists(
536
702
    al: BTreeSet<DisfavouredKey>,
537
702
    bl: BTreeSet<DisfavouredKey>,
538
702
) -> BTreeSet<DisfavouredKey> {
539
702
    //eprintln!("INTERSECT:");
540
702
    //for ai in &al { eprintln!("A: {}", ai); }
541
702
    //for bi in &bl { eprintln!("B: {}", bi); }
542
702

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

            
548
1425
    let mut inputs: [_; 2] = [al, bl].map(|input| input.into_iter().peekable());
549
702
    let mut output = BTreeSet::new();
550

            
551
    // The BTreeSets produce items in sort order.
552
    //
553
    // We maintain the following invariants (valid at the top of the loop):
554
    //
555
    //   For every possible key *strictly earlier* than those remaining in either input,
556
    //   the output contains the key iff it was in the intersection.
557
    //
558
    //   No other keys appear in the output.
559
    //
560
    // We peek at the next two items.  The possible cases are:
561
    //
562
    //   0. One or both inputs is used up.  In that case none of any remaining input
563
    //      can be in the intersection and we are done.
564
    //
565
    //   1. The two inputs have the same next item.  In that case the item is in the
566
    //      intersection.  If the inputs are minimal, no children of that item can appear
567
    //      in either input, so we can make our own output minimal without thinking any
568
    //      more about this item from the point of view of either list.
569
    //
570
    //   2. One of the inputs is a prefix of the other.  In this case the longer item is
571
    //      in the intersection - as are all subsequent items from the same input which
572
    //      also share that prefix.  Then, we must discard the shorter item (which denotes
573
    //      the whole subspace of which only part is in the intersection).
574
    //
575
    //   3. Otherwise, the earlier item is definitely not in the intersection and
576
    //      we can munch it.
577

            
578
    // Peek one from each, while we can.
579
5192
    while let Ok(items) = {
580
5192
        // Ideally we would use array::try_map but it's nightly-only
581
5192
        <[_; 2]>::try_from(
582
5192
            inputs
583
5192
                .iter_mut()
584
10505
                .flat_map(|input: &'_ mut _| input.peek()) // keep the Somes
585
5192
                .collect::<Vec<_>>(), // if we had 2 Somes we can make a [_; 2] from this
586
5192
        )
587
5192
    } {
588
9080
        let shorter_len = items.iter().map(|i| i.path.len()).min().expect("wrong #");
589
4490
        let earlier_i = items
590
4490
            .iter()
591
4490
            .enumerate()
592
9080
            .min_by_key(|&(_i, item)| *item)
593
4490
            .expect("wrong #")
594
4490
            .0;
595
4490
        let later_i = 1 - earlier_i;
596
4490

            
597
4490
        if items.iter().all_equal() {
598
            // Case 0. above.
599
            //
600
            // Take the identical items off the front of both iters,
601
            // and put one into the output (the last will do nicely).
602
            //dbg!(items);
603
26
            let item = inputs
604
26
                .iter_mut()
605
65
                .map(|input| input.next().expect("but peeked"))
606
26
                .last()
607
26
                .expect("wrong #");
608
26
            output.insert(item);
609
26
            continue;
610
4464
        } else if items
611
4464
            .iter()
612
9015
            .map(|item| &item.path[0..shorter_len])
613
4464
            .all_equal()
614
        {
615
            // Case 2.  One is a prefix of the other.   earlier_i is the shorter one.
616
8
            let shorter_item = items[earlier_i];
617
8
            let prefix = shorter_item.path.clone(); // borrowck can't prove disjointness
618

            
619
            // Keep copying items from the side with the longer entries,
620
            // so long as they fall within (have the prefix of) the shorter entry.
621
            //dbg!(items, shorter_item, &prefix);
622
24
            while let Some(longer_item) = inputs[later_i].peek() {
623
24
                if !longer_item.path.starts_with(&prefix) {
624
8
                    break;
625
16
                }
626
16
                let longer_item = inputs[later_i].next().expect("but peeked");
627
16
                output.insert(longer_item);
628
            }
629
            // We've "used up" the shorter item.
630
8
            let _ = inputs[earlier_i].next().expect("but peeked");
631
4456
        } else {
632
4456
            // Case 3.  The items are just different.  Eat the earlier one.
633
4456
            //dbg!(items, earlier_i);
634
4456
            let _ = inputs[earlier_i].next().expect("but peeked");
635
4456
        }
636
    }
637
    // Case 0.  At least one of the lists is empty, giving Err() from the array
638

            
639
    //for oi in &ol { eprintln!("O: {}", oi); }
640
702
    output
641
702
}
642

            
643
impl Display for DisfavouredKey {
644
18
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
645
        use PathEntry as PE;
646
18
        if self.path.is_empty() {
647
            // shouldn't happen with calls outside this module, and shouldn't be used inside
648
            // but handle it anyway
649
2
            write!(f, r#""""#)?;
650
        } else {
651
16
            let delims = chain!(iter::once(""), iter::repeat("."));
652
22
            for (delim, ent) in izip!(delims, self.path.iter()) {
653
22
                match ent {
654
4
                    PE::ArrayIndex(index) => write!(f, "[{}]", index)?,
655
18
                    PE::MapEntry(s) => {
656
18
                        if ok_unquoted(s) {
657
14
                            write!(f, "{}{}", delim, s)?;
658
                        } else {
659
4
                            write!(f, "{}{:?}", delim, s)?;
660
                        }
661
                    }
662
                }
663
            }
664
        }
665
18
        Ok(())
666
18
    }
667
}
668

            
669
/// Would `s` be OK to use unquoted as a key in a TOML file?
670
42
fn ok_unquoted(s: &str) -> bool {
671
42
    let mut chars = s.chars();
672
42
    if let Some(c) = chars.next() {
673
40
        c.is_ascii_alphanumeric()
674
58
            && chars.all(|c| c == '_' || c == '-' || c.is_ascii_alphanumeric())
675
    } else {
676
2
        false
677
    }
678
42
}
679

            
680
#[cfg(test)]
681
#[allow(unreachable_pub)] // impl_standard_builder wants to make pub fns
682
mod test {
683
    // @@ begin test lint list maintained by maint/add_warning @@
684
    #![allow(clippy::bool_assert_comparison)]
685
    #![allow(clippy::clone_on_copy)]
686
    #![allow(clippy::dbg_macro)]
687
    #![allow(clippy::mixed_attributes_style)]
688
    #![allow(clippy::print_stderr)]
689
    #![allow(clippy::print_stdout)]
690
    #![allow(clippy::single_char_pattern)]
691
    #![allow(clippy::unwrap_used)]
692
    #![allow(clippy::unchecked_duration_subtraction)]
693
    #![allow(clippy::useless_vec)]
694
    #![allow(clippy::needless_pass_by_value)]
695
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
696
    use super::*;
697
    use crate::*;
698
    use derive_builder::Builder;
699
    use serde::{Deserialize, Serialize};
700

            
701
    fn parse_test_set(l: &[&str]) -> BTreeSet<DisfavouredKey> {
702
        l.iter()
703
            .map(|s| DisfavouredKey {
704
                path: s
705
                    .split('.')
706
                    .map(|s| PathEntry::MapEntry(s.into()))
707
                    .collect_vec(),
708
            })
709
            .collect()
710
    }
711

            
712
    #[test]
713
    #[rustfmt::skip] // preserve the layout so we can match vertically by eye
714
    fn test_intersect_unrecognized_list() {
715
        let chk = |a, b, exp| {
716
            let got = intersect_unrecognized_lists(parse_test_set(a), parse_test_set(b));
717
            let exp = parse_test_set(exp);
718
            assert_eq! { got, exp };
719

            
720
            let got = intersect_unrecognized_lists(parse_test_set(b), parse_test_set(a));
721
            assert_eq! { got, exp };
722
        };
723

            
724
        chk(&[ "a", "b",     ],
725
            &[ "a",      "c" ],
726
            &[ "a" ]);
727

            
728
        chk(&[ "a", "b",      "d" ],
729
            &[ "a",      "c", "d" ],
730
            &[ "a",           "d" ]);
731

            
732
        chk(&[ "x.a", "x.b",     ],
733
            &[ "x.a",      "x.c" ],
734
            &[ "x.a" ]);
735

            
736
        chk(&[ "t", "u", "v",          "w"     ],
737
            &[ "t",      "v.a", "v.b",     "x" ],
738
            &[ "t",      "v.a", "v.b",         ]);
739

            
740
        chk(&[ "t",      "v",              "x" ],
741
            &[ "t", "u", "v.a", "v.b", "w"     ],
742
            &[ "t",      "v.a", "v.b",         ]);
743
    }
744

            
745
    #[test]
746
    #[allow(clippy::bool_assert_comparison)] // much clearer this way IMO
747
    fn test_ok_unquoted() {
748
        assert_eq! { false, ok_unquoted("") };
749
        assert_eq! { false, ok_unquoted("_") };
750
        assert_eq! { false, ok_unquoted(".") };
751
        assert_eq! { false, ok_unquoted("-") };
752
        assert_eq! { false, ok_unquoted("_a") };
753
        assert_eq! { false, ok_unquoted(".a") };
754
        assert_eq! { false, ok_unquoted("-a") };
755
        assert_eq! { false, ok_unquoted("a.") };
756
        assert_eq! { true, ok_unquoted("a") };
757
        assert_eq! { true, ok_unquoted("1") };
758
        assert_eq! { true, ok_unquoted("z") };
759
        assert_eq! { true, ok_unquoted("aa09_-") };
760
    }
761

            
762
    #[test]
763
    fn test_display_key() {
764
        let chk = |exp, path: &[PathEntry]| {
765
            assert_eq! { DisfavouredKey { path: path.into() }.to_string(), exp };
766
        };
767
        let me = |s: &str| PathEntry::MapEntry(s.into());
768
        use PathEntry::ArrayIndex as AI;
769

            
770
        chk(r#""""#, &[]);
771
        chk(r#""@""#, &[me("@")]);
772
        chk(r#""\\""#, &[me(r#"\"#)]);
773
        chk(r#"foo"#, &[me("foo")]);
774
        chk(r#"foo.bar"#, &[me("foo"), me("bar")]);
775
        chk(r#"foo[10]"#, &[me("foo"), AI(10)]);
776
        chk(r#"[10].bar"#, &[AI(10), me("bar")]); // weird
777
    }
778

            
779
    #[derive(Debug, Clone, Builder, Eq, PartialEq)]
780
    #[builder(build_fn(error = "ConfigBuildError"))]
781
    #[builder(derive(Debug, Serialize, Deserialize))]
782
    struct TestConfigA {
783
        #[builder(default)]
784
        a: String,
785
    }
786
    impl_standard_builder! { TestConfigA }
787
    impl TopLevel for TestConfigA {
788
        type Builder = TestConfigABuilder;
789
    }
790

            
791
    #[derive(Debug, Clone, Builder, Eq, PartialEq)]
792
    #[builder(build_fn(error = "ConfigBuildError"))]
793
    #[builder(derive(Debug, Serialize, Deserialize))]
794
    struct TestConfigB {
795
        #[builder(default)]
796
        b: String,
797

            
798
        #[builder(default)]
799
        old: bool,
800
    }
801
    impl_standard_builder! { TestConfigB }
802
    impl TopLevel for TestConfigB {
803
        type Builder = TestConfigBBuilder;
804
        const DEPRECATED_KEYS: &'static [&'static str] = &["old"];
805
    }
806

            
807
    #[test]
808
    fn test_resolve() {
809
        let test_data = r#"
810
            wombat = 42
811
            a = "hi"
812
            old = true
813
        "#;
814
        let cfg = {
815
            let mut sources = crate::ConfigurationSources::new_empty();
816
            sources.push_source(
817
                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
818
                crate::sources::MustRead::MustRead,
819
            );
820
            sources.load().unwrap()
821
        };
822

            
823
        let _: (TestConfigA, TestConfigB) = resolve_ignore_warnings(cfg.clone()).unwrap();
824

            
825
        let resolved: ResolutionResults<(TestConfigA, TestConfigB)> =
826
            resolve_return_results(cfg).unwrap();
827
        let (a, b) = resolved.value;
828

            
829
        let mk_strings =
830
            |l: Vec<DisfavouredKey>| l.into_iter().map(|ik| ik.to_string()).collect_vec();
831

            
832
        let ign = mk_strings(resolved.unrecognized);
833
        let depr = mk_strings(resolved.deprecated);
834

            
835
        assert_eq! { &a, &TestConfigA { a: "hi".into() } };
836
        assert_eq! { &b, &TestConfigB { b: "".into(), old: true } };
837
        assert_eq! { ign, &["wombat"] };
838
        assert_eq! { depr, &["old"] };
839

            
840
        let _ = TestConfigA::builder();
841
        let _ = TestConfigB::builder();
842
    }
843

            
844
    #[derive(Debug, Clone, Builder, Eq, PartialEq)]
845
    #[builder(build_fn(error = "ConfigBuildError"))]
846
    #[builder(derive(Debug, Serialize, Deserialize))]
847
    struct TestConfigC {
848
        #[builder(default)]
849
        c: u32,
850
    }
851
    impl_standard_builder! { TestConfigC }
852
    impl TopLevel for TestConfigC {
853
        type Builder = TestConfigCBuilder;
854
    }
855

            
856
    #[test]
857
    fn build_error() {
858
        // Make sure that errors are propagated correctly.
859
        let test_data = r#"
860
            # wombat is not a number.
861
            c = "wombat"
862
            # this _would_ be unrecognized, but for the errors.
863
            persimmons = "sweet"
864
        "#;
865
        // suppress a dead-code warning.
866
        let _b = TestConfigC::builder();
867

            
868
        let cfg = {
869
            let mut sources = crate::ConfigurationSources::new_empty();
870
            sources.push_source(
871
                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
872
                crate::sources::MustRead::MustRead,
873
            );
874
            sources.load().unwrap()
875
        };
876

            
877
        {
878
            // First try "A", then "C".
879
            let res1: Result<ResolutionResults<(TestConfigA, TestConfigC)>, _> =
880
                resolve_return_results(cfg.clone());
881
            assert!(res1.is_err());
882
            assert!(matches!(res1, Err(ConfigResolveError::Deserialize(_))));
883
        }
884
        {
885
            // Now the other order: first try "C", then "A".
886
            let res2: Result<ResolutionResults<(TestConfigC, TestConfigA)>, _> =
887
                resolve_return_results(cfg.clone());
888
            assert!(res2.is_err());
889
            assert!(matches!(res2, Err(ConfigResolveError::Deserialize(_))));
890
        }
891
        // Try manually, to make sure unrecognized fields are removed.
892
        let mut ctx = ResolveContext {
893
            input: cfg,
894
            unrecognized: UnrecognizedKeys::AllKeys,
895
        };
896
        let _res3 = TestConfigA::resolve(&mut ctx);
897
        // After resolving A, some fields are unrecognized.
898
        assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if !k.is_empty()));
899
        {
900
            let res4 = TestConfigC::resolve(&mut ctx);
901
            assert!(matches!(res4, Err(ConfigResolveError::Deserialize(_))));
902
        }
903
        {
904
            // After resolving C with an error, the unrecognized-field list is cleared.
905
            assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if k.is_empty()));
906
        }
907
    }
908
}