foundry_config/providers/
warnings.rs

1use crate::{Config, Warning, DEPRECATIONS};
2use figment::{
3    value::{Dict, Map, Value},
4    Error, Figment, Metadata, Profile, Provider,
5};
6use std::collections::BTreeMap;
7
8/// Generate warnings for unknown sections and deprecated keys
9pub struct WarningsProvider<P> {
10    provider: P,
11    profile: Profile,
12    old_warnings: Result<Vec<Warning>, Error>,
13}
14
15impl<P: Provider> WarningsProvider<P> {
16    const WARNINGS_KEY: &'static str = "__warnings";
17
18    /// Creates a new warnings provider.
19    pub fn new(
20        provider: P,
21        profile: impl Into<Profile>,
22        old_warnings: Result<Vec<Warning>, Error>,
23    ) -> Self {
24        Self { provider, profile: profile.into(), old_warnings }
25    }
26
27    /// Creates a new figment warnings provider.
28    pub fn for_figment(provider: P, figment: &Figment) -> Self {
29        let old_warnings = {
30            let warnings_res = figment.extract_inner(Self::WARNINGS_KEY);
31            if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) {
32                Ok(vec![])
33            } else {
34                warnings_res
35            }
36        };
37        Self::new(provider, figment.profile().clone(), old_warnings)
38    }
39
40    /// Collects all warnings.
41    pub fn collect_warnings(&self) -> Result<Vec<Warning>, Error> {
42        let data = self.provider.data().unwrap_or_default();
43
44        let mut out = self.old_warnings.clone()?;
45
46        // Add warning for unknown sections.
47        out.extend(
48            data.keys()
49                .filter(|k| {
50                    **k != Config::PROFILE_SECTION &&
51                        !Config::STANDALONE_SECTIONS.iter().any(|s| s == k)
52                })
53                .map(|unknown_section| {
54                    let source = self.provider.metadata().source.map(|s| s.to_string());
55                    Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
56                }),
57        );
58
59        // Add warning for deprecated keys.
60        let deprecated_key_warning = |key| {
61            DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
62                if key == *deprecated_key {
63                    Some(Warning::DeprecatedKey {
64                        old: deprecated_key.to_string(),
65                        new: new_value.to_string(),
66                    })
67                } else {
68                    None
69                }
70            })
71        };
72        let profiles = data
73            .iter()
74            .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
75            .map(|(_, dict)| dict);
76        out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning));
77        out.extend(
78            profiles
79                .filter_map(|dict| dict.get(self.profile.as_str().as_str()))
80                .filter_map(Value::as_dict)
81                .flat_map(BTreeMap::keys)
82                .filter_map(deprecated_key_warning),
83        );
84
85        Ok(out)
86    }
87}
88
89impl<P: Provider> Provider for WarningsProvider<P> {
90    fn metadata(&self) -> Metadata {
91        if let Some(source) = self.provider.metadata().source {
92            Metadata::from("Warnings", source)
93        } else {
94            Metadata::named("Warnings")
95        }
96    }
97
98    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
99        let warnings = self.collect_warnings()?;
100        Ok(Map::from([(
101            self.profile.clone(),
102            Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
103        )]))
104    }
105
106    fn profile(&self) -> Option<Profile> {
107        Some(self.profile.clone())
108    }
109}