Skip to main content

tor_config/
load.rs

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
97use std::collections::BTreeSet;
98use std::fmt::{self, Display};
99use std::iter;
100use std::mem;
101
102use itertools::{Itertools, chain, izip};
103use serde::de::DeserializeOwned;
104use thiserror::Error;
105use tracing::warn;
106
107use crate::{ConfigBuildError, ConfigurationTree};
108
109/// Error resolving a configuration (during deserialize, or build)
110#[derive(Error, Debug)]
111#[non_exhaustive]
112pub 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.
123pub 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.
135pub 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.)
152pub 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.)
186pub 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
223pub 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
242macro_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            fn resolve(cfg: &mut ResolveContext) -> Result<Self, ConfigResolveError> {
252                Ok(( $( $A::resolve(cfg)?, )* ))
253            }
254            fn enumerate_deprecated_keys<NF>(f: &mut NF)
255            where NF: FnMut(&'static [&'static str]) {
256                $( $A::enumerate_deprecated_keys(f); )*
257            }
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.
264define_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.
270pub 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)]
290enum 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}
299use UnrecognizedKeys as UK;
300
301impl UnrecognizedKeys {
302    /// Does it represent the empty set
303    fn is_empty(&self) -> bool {
304        match self {
305            UK::AllKeys => false,
306            UK::These(ign) => ign.is_empty(),
307        }
308    }
309
310    /// Update in place, intersecting with `other`
311    fn intersect_with(&mut self, other: BTreeSet<DisfavouredKey>) {
312        match self {
313            UK::AllKeys => *self = UK::These(other),
314            UK::These(self_) => {
315                let tign = mem::take(self_);
316                *self_ = intersect_unrecognized_lists(tign, other);
317            }
318        }
319    }
320
321    /// Remove every element of this set.
322    fn clear(&mut self) {
323        *self = UK::These(BTreeSet::new());
324    }
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)]
333pub 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)]
340enum 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]
357pub 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
368impl Default for ConfigResolveOptions {
369    fn default() -> Self {
370        Self {
371            want_disfavoured: true,
372            want_output_tree: false,
373        }
374    }
375}
376
377/// Deserialize and build overall configuration from config sources
378///
379/// Inner function used by all the `resolve_*` family
380fn resolve_inner<T>(
381    input: ConfigurationTree,
382    options: &ConfigResolveOptions,
383) -> Result<ResolutionResults<T>, ConfigResolveError>
384where
385    T: Resolvable,
386{
387    let mut deprecated = BTreeSet::new();
388
389    if options.want_disfavoured {
390        T::enumerate_deprecated_keys(&mut |l: &[&str]| {
391            for key in l {
392                match input.0.find_value(key) {
393                    Err(_) => {}
394                    Ok(_) => {
395                        deprecated.insert(key);
396                    }
397                }
398            }
399        });
400    }
401
402    let mut lc = ResolveContext {
403        input,
404        unrecognized: if options.want_disfavoured {
405            UK::AllKeys
406        } else {
407            UK::These(BTreeSet::new())
408        },
409
410        output_tree: if options.want_output_tree {
411            Some(ConfigurationTree::default())
412        } else {
413            None
414        },
415    };
416
417    let value = Resolvable::resolve(&mut lc)?;
418
419    let unrecognized = match lc.unrecognized {
420        UK::AllKeys => panic!("all unrecognized, as if we had processed nothing"),
421        UK::These(ign) => ign,
422    }
423    .into_iter()
424    .filter(|ip| !ip.path.is_empty())
425    .collect_vec();
426
427    let deprecated = deprecated
428        .into_iter()
429        .map(|key| {
430            let path = key
431                .split('.')
432                .map(|e| PathEntry::MapEntry(e.into()))
433                .collect_vec();
434            DisfavouredKey { path }
435        })
436        .collect_vec();
437
438    Ok(ResolutionResults {
439        value,
440        unrecognized,
441        deprecated,
442        output_tree: lc.output_tree,
443    })
444}
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).
458pub fn resolve<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
459where
460    T: Resolvable,
461{
462    let options: ConfigResolveOptions = ConfigResolveOptions {
463        want_disfavoured: true,
464        want_output_tree: false,
465    };
466    let ResolutionResults {
467        value,
468        unrecognized,
469        deprecated,
470        ..
471    } = resolve_inner(input, &options)?;
472    for depr in deprecated {
473        warn!("deprecated configuration key: {}", &depr);
474    }
475    for ign in unrecognized {
476        warn!("unrecognized configuration key: {}", &ign);
477    }
478    Ok(value)
479}
480
481/// Deserialize and build overall configuration, reporting unrecognized keys in the return value
482pub fn resolve_return_results<T>(
483    input: ConfigurationTree,
484    options: &ConfigResolveOptions,
485) -> Result<ResolutionResults<T>, ConfigResolveError>
486where
487    T: Resolvable,
488{
489    resolve_inner(input, options)
490}
491
492/// Results of a successful [`resolve_return_results`].
493#[derive(Debug, Clone)]
494#[non_exhaustive]
495pub 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
513pub fn resolve_ignore_warnings<T>(input: ConfigurationTree) -> Result<T, ConfigResolveError>
514where
515    T: Resolvable,
516{
517    let options: ConfigResolveOptions = ConfigResolveOptions {
518        want_disfavoured: false,
519        want_output_tree: false,
520    };
521    Ok(resolve_inner(input, &options)?.value)
522}
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.)
528struct 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}
534impl<'de, T> serde::Deserialize<'de> for Des<T>
535where
536    T: serde::Deserialize<'de>,
537{
538    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
539    where
540        D: serde::Deserializer<'de>,
541    {
542        let mut nign = BTreeSet::new();
543        let mut recorder = |path: serde_ignored::Path<'_>| {
544            nign.insert(copy_path(&path));
545        };
546        let deser = serde_ignored::Deserializer::new(deserializer, &mut recorder);
547        let ret = serde::Deserialize::deserialize(deser);
548        Ok(Des { nign, value: ret? })
549    }
550}
551
552impl<T> Resolvable for T
553where
554    T: TopLevel,
555    T::Builder: Builder<Built = Self> + ConfigBuilder + Clone + serde::Serialize,
556{
557    fn resolve(input: &mut ResolveContext) -> Result<T, ConfigResolveError> {
558        let deser = &input.input;
559        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            let want_unrecognized = !input.unrecognized.is_empty();
564            if !want_unrecognized {
565                deser.0.extract_lossy()
566            } else {
567                let ret: Result<Des<<T as TopLevel>::Builder>, _> = deser.0.extract_lossy();
568
569                match ret {
570                    Ok(Des { nign, value }) => {
571                        input.unrecognized.intersect_with(nign);
572                        Ok(value)
573                    }
574                    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                        input.unrecognized.clear();
578                        Err(e)
579                    }
580                }
581            }
582        };
583        let builder = builder.map_err(crate::ConfigError::from_cfg_err)?;
584
585        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        }
590
591        let built = builder.build()?;
592        Ok(built)
593    }
594
595    fn enumerate_deprecated_keys<NF>(f: &mut NF)
596    where
597        NF: FnMut(&'static [&'static str]),
598    {
599        f(T::DEPRECATED_KEYS);
600    }
601}
602
603/// Turns a [`serde_ignored::Path`] (which is borrowed) into an owned `DisfavouredKey`
604fn copy_path(mut path: &serde_ignored::Path) -> DisfavouredKey {
605    use PathEntry as PE;
606    use serde_ignored::Path as SiP;
607
608    let mut descend = vec![];
609    loop {
610        let (new_path, ent) = match path {
611            SiP::Root => break,
612            SiP::Seq { parent, index } => (parent, Some(PE::ArrayIndex(*index))),
613            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        descend.extend(ent);
619        path = new_path;
620    }
621    descend.reverse();
622    DisfavouredKey { path: descend }
623}
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).
642fn intersect_unrecognized_lists(
643    al: BTreeSet<DisfavouredKey>,
644    bl: BTreeSet<DisfavouredKey>,
645) -> 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    let mut inputs: [_; 2] = [al, bl].map(|input| input.into_iter().peekable());
656    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    while let Ok(items) = {
687        // Ideally we would use array::try_map but it's nightly-only
688        <[_; 2]>::try_from(
689            inputs
690                .iter_mut()
691                .flat_map(|input: &'_ mut _| input.peek()) // keep the Somes
692                .collect::<Vec<_>>(), // if we had 2 Somes we can make a [_; 2] from this
693        )
694    } {
695        let shorter_len = items.iter().map(|i| i.path.len()).min().expect("wrong #");
696        let earlier_i = items
697            .iter()
698            .enumerate()
699            .min_by_key(|&(_i, item)| *item)
700            .expect("wrong #")
701            .0;
702        let later_i = 1 - earlier_i;
703
704        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            let item = inputs
711                .iter_mut()
712                .map(|input| input.next().expect("but peeked"))
713                .next_back()
714                .expect("wrong #");
715            output.insert(item);
716            continue;
717        } else if items
718            .iter()
719            .map(|item| &item.path[0..shorter_len])
720            .all_equal()
721        {
722            // Case 2.  One is a prefix of the other.   earlier_i is the shorter one.
723            let shorter_item = items[earlier_i];
724            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            while let Some(longer_item) = inputs[later_i].peek() {
730                if !longer_item.path.starts_with(&prefix) {
731                    break;
732                }
733                let longer_item = inputs[later_i].next().expect("but peeked");
734                output.insert(longer_item);
735            }
736            // We've "used up" the shorter item.
737            let _ = inputs[earlier_i].next().expect("but peeked");
738        } else {
739            // Case 3.  The items are just different.  Eat the earlier one.
740            //dbg!(items, earlier_i);
741            let _ = inputs[earlier_i].next().expect("but peeked");
742        }
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    output
748}
749
750impl Display for DisfavouredKey {
751    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
752        use PathEntry as PE;
753        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            write!(f, r#""""#)?;
757        } else {
758            let delims = chain!(iter::once(""), iter::repeat("."));
759            for (delim, ent) in izip!(delims, self.path.iter()) {
760                match ent {
761                    PE::ArrayIndex(index) => write!(f, "[{}]", index)?,
762                    PE::MapEntry(s) => {
763                        if ok_unquoted(s) {
764                            write!(f, "{}{}", delim, s)?;
765                        } else {
766                            write!(f, "{}{:?}", delim, s)?;
767                        }
768                    }
769                }
770            }
771        }
772        Ok(())
773    }
774}
775
776/// Would `s` be OK to use unquoted as a key in a TOML file?
777fn ok_unquoted(s: &str) -> bool {
778    let mut chars = s.chars();
779    if let Some(c) = chars.next() {
780        c.is_ascii_alphanumeric()
781            && chars.all(|c| c == '_' || c == '-' || c.is_ascii_alphanumeric())
782    } else {
783        false
784    }
785}
786
787#[cfg(test)]
788#[allow(unreachable_pub)] // impl_standard_builder wants to make pub fns
789mod 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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
803    use super::*;
804    use crate::*;
805    use derive_deftly::Deftly;
806
807    fn parse_test_set(l: &[&str]) -> BTreeSet<DisfavouredKey> {
808        l.iter()
809            .map(|s| DisfavouredKey {
810                path: s
811                    .split('.')
812                    .map(|s| PathEntry::MapEntry(s.into()))
813                    .collect_vec(),
814            })
815            .collect()
816    }
817
818    #[test]
819    #[rustfmt::skip] // preserve the layout so we can match vertically by eye
820    fn test_intersect_unrecognized_list() {
821        let chk = |a, b, exp| {
822            let got = intersect_unrecognized_lists(parse_test_set(a), parse_test_set(b));
823            let exp = parse_test_set(exp);
824            assert_eq! { got, exp };
825
826            let got = intersect_unrecognized_lists(parse_test_set(b), parse_test_set(a));
827            assert_eq! { got, exp };
828        };
829
830        chk(&[ "a", "b",     ],
831            &[ "a",      "c" ],
832            &[ "a" ]);
833
834        chk(&[ "a", "b",      "d" ],
835            &[ "a",      "c", "d" ],
836            &[ "a",           "d" ]);
837
838        chk(&[ "x.a", "x.b",     ],
839            &[ "x.a",      "x.c" ],
840            &[ "x.a" ]);
841
842        chk(&[ "t", "u", "v",          "w"     ],
843            &[ "t",      "v.a", "v.b",     "x" ],
844            &[ "t",      "v.a", "v.b",         ]);
845
846        chk(&[ "t",      "v",              "x" ],
847            &[ "t", "u", "v.a", "v.b", "w"     ],
848            &[ "t",      "v.a", "v.b",         ]);
849    }
850
851    #[test]
852    #[allow(clippy::bool_assert_comparison)] // much clearer this way IMO
853    fn test_ok_unquoted() {
854        assert_eq! { false, 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("_a") };
859        assert_eq! { false, ok_unquoted(".a") };
860        assert_eq! { false, ok_unquoted("-a") };
861        assert_eq! { false, ok_unquoted("a.") };
862        assert_eq! { true, ok_unquoted("a") };
863        assert_eq! { true, ok_unquoted("1") };
864        assert_eq! { true, ok_unquoted("z") };
865        assert_eq! { true, ok_unquoted("aa09_-") };
866    }
867
868    #[test]
869    fn test_display_key() {
870        let chk = |exp, path: &[PathEntry]| {
871            assert_eq! { DisfavouredKey { path: path.into() }.to_string(), exp };
872        };
873        let me = |s: &str| PathEntry::MapEntry(s.into());
874        use PathEntry::ArrayIndex as AI;
875
876        chk(r#""""#, &[]);
877        chk(r#""@""#, &[me("@")]);
878        chk(r#""\\""#, &[me(r#"\"#)]);
879        chk(r#"foo"#, &[me("foo")]);
880        chk(r#"foo.bar"#, &[me("foo"), me("bar")]);
881        chk(r#"foo[10]"#, &[me("foo"), AI(10)]);
882        chk(r#"[10].bar"#, &[AI(10), me("bar")]); // weird
883    }
884
885    #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
886    #[derive_deftly(TorConfig)]
887    struct TestConfigA {
888        #[deftly(tor_config(default))]
889        a: String,
890    }
891    impl TopLevel for TestConfigA {
892        type Builder = TestConfigABuilder;
893    }
894
895    #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
896    #[derive_deftly(TorConfig)]
897    struct TestConfigB {
898        #[deftly(tor_config(default))]
899        b: String,
900
901        #[deftly(tor_config(default))]
902        old: bool,
903    }
904    impl TopLevel for TestConfigB {
905        type Builder = TestConfigBBuilder;
906        const DEPRECATED_KEYS: &'static [&'static str] = &["old"];
907    }
908
909    #[test]
910    fn test_resolve() {
911        let test_data = r#"
912            wombat = 42
913            a = "hi"
914            old = true
915        "#;
916        let cfg = {
917            let mut sources = crate::ConfigurationSources::new_empty();
918            sources.push_source(
919                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
920                crate::sources::MustRead::MustRead,
921            );
922            sources.load().unwrap()
923        };
924
925        let _: (TestConfigA, TestConfigB) = resolve_ignore_warnings(cfg.clone()).unwrap();
926
927        let resolved: ResolutionResults<(TestConfigA, TestConfigB)> =
928            resolve_return_results(cfg, &Default::default()).unwrap();
929        let (a, b) = resolved.value;
930
931        let mk_strings =
932            |l: Vec<DisfavouredKey>| l.into_iter().map(|ik| ik.to_string()).collect_vec();
933
934        let ign = mk_strings(resolved.unrecognized);
935        let depr = mk_strings(resolved.deprecated);
936
937        assert_eq! { &a, &TestConfigA { a: "hi".into() } };
938        assert_eq! { &b, &TestConfigB { b: "".into(), old: true } };
939        assert_eq! { ign, &["wombat"] };
940        assert_eq! { depr, &["old"] };
941
942        let _ = TestConfigA::builder();
943        let _ = TestConfigB::builder();
944    }
945
946    #[derive(Debug, Clone, Deftly, Eq, PartialEq)]
947    #[derive_deftly(TorConfig)]
948    struct TestConfigC {
949        #[deftly(tor_config(default))]
950        c: u32,
951    }
952    impl TopLevel for TestConfigC {
953        type Builder = TestConfigCBuilder;
954    }
955
956    #[test]
957    fn build_error() {
958        // Make sure that errors are propagated correctly.
959        let test_data = r#"
960            # wombat is not a number.
961            c = "wombat"
962            # this _would_ be unrecognized, but for the errors.
963            persimmons = "sweet"
964        "#;
965        // suppress a dead-code warning.
966        let _b = TestConfigC::builder();
967
968        let cfg = {
969            let mut sources = crate::ConfigurationSources::new_empty();
970            sources.push_source(
971                crate::ConfigurationSource::from_verbatim(test_data.to_string()),
972                crate::sources::MustRead::MustRead,
973            );
974            sources.load().unwrap()
975        };
976
977        {
978            // First try "A", then "C".
979            let res1: Result<ResolutionResults<(TestConfigA, TestConfigC)>, _> =
980                resolve_return_results(cfg.clone(), &Default::default());
981            assert!(res1.is_err());
982            assert!(matches!(res1, Err(ConfigResolveError::Deserialize(_))));
983        }
984        {
985            // Now the other order: first try "C", then "A".
986            let res2: Result<ResolutionResults<(TestConfigC, TestConfigA)>, _> =
987                resolve_return_results(cfg.clone(), &Default::default());
988            assert!(res2.is_err());
989            assert!(matches!(res2, Err(ConfigResolveError::Deserialize(_))));
990        }
991        // Try manually, to make sure unrecognized fields are removed.
992        let mut ctx = ResolveContext {
993            input: cfg,
994            unrecognized: UnrecognizedKeys::AllKeys,
995            output_tree: None,
996        };
997        let _res3 = TestConfigA::resolve(&mut ctx);
998        // After resolving A, some fields are unrecognized.
999        assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if !k.is_empty()));
1000        {
1001            let res4 = TestConfigC::resolve(&mut ctx);
1002            assert!(matches!(res4, Err(ConfigResolveError::Deserialize(_))));
1003        }
1004        {
1005            // After resolving C with an error, the unrecognized-field list is cleared.
1006            assert!(matches!(&ctx.unrecognized, UnrecognizedKeys::These(k) if k.is_empty()));
1007        }
1008    }
1009}