foundry_config/providers/
warnings.rs1use crate::{Config, DEPRECATIONS, Warning};
2use figment::{
3 Error, Figment, Metadata, Profile, Provider,
4 value::{Dict, Map, Value},
5};
6use heck::ToSnakeCase;
7use std::collections::BTreeMap;
8
9pub struct WarningsProvider<P> {
11 provider: P,
12 profile: Profile,
13 old_warnings: Result<Vec<Warning>, Error>,
14}
15
16impl<P: Provider> WarningsProvider<P> {
17 const WARNINGS_KEY: &'static str = "__warnings";
18
19 pub fn new(
21 provider: P,
22 profile: impl Into<Profile>,
23 old_warnings: Result<Vec<Warning>, Error>,
24 ) -> Self {
25 Self { provider, profile: profile.into(), old_warnings }
26 }
27
28 pub fn for_figment(provider: P, figment: &Figment) -> Self {
30 let old_warnings = {
31 let warnings_res = figment.extract_inner(Self::WARNINGS_KEY);
32 if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) {
33 Ok(vec![])
34 } else {
35 warnings_res
36 }
37 };
38 Self::new(provider, figment.profile().clone(), old_warnings)
39 }
40
41 pub fn collect_warnings(&self) -> Result<Vec<Warning>, Error> {
43 let data = self.provider.data().unwrap_or_default();
44
45 let mut out = self.old_warnings.clone()?;
46
47 out.extend(
49 data.keys()
50 .filter(|k| {
51 **k != Config::PROFILE_SECTION
52 && **k != Config::EXTERNAL_SECTION
53 && !Config::STANDALONE_SECTIONS.iter().any(|s| s == k)
54 })
55 .map(|unknown_section| {
56 let source = self.provider.metadata().source.map(|s| s.to_string());
57 Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
58 }),
59 );
60
61 let deprecated_key_warning = |key| {
63 DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
64 if key == *deprecated_key {
65 Some(Warning::DeprecatedKey {
66 old: deprecated_key.to_string(),
67 new: new_value.to_string(),
68 })
69 } else {
70 None
71 }
72 })
73 };
74 let profiles = data
75 .iter()
76 .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
77 .map(|(_, dict)| dict);
78
79 out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning));
80 out.extend(
81 profiles
82 .clone()
83 .filter_map(|dict| dict.get(self.profile.as_str().as_str()))
84 .filter_map(Value::as_dict)
85 .flat_map(BTreeMap::keys)
86 .filter_map(deprecated_key_warning),
87 );
88
89 if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data()
91 && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE)
92 {
93 let allowed_keys: std::collections::BTreeSet<String> =
94 default_dict.keys().cloned().collect();
95 for profile_map in profiles {
96 for (profile, value) in profile_map {
97 let Some(profile_dict) = value.as_dict() else {
98 continue;
99 };
100
101 let source = self
102 .provider
103 .metadata()
104 .source
105 .map(|s| s.to_string())
106 .unwrap_or(Config::FILE_NAME.to_string());
107 for key in profile_dict.keys() {
108 let is_not_deprecated =
109 !DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key);
110 let is_not_allowed = !allowed_keys.contains(key)
111 && !allowed_keys.contains(&key.to_snake_case());
112 let is_not_reserved = key != "extends" && key != Self::WARNINGS_KEY;
113 let is_not_backward_compatible = key != "solc_version";
114
115 if is_not_deprecated
116 && is_not_allowed
117 && is_not_reserved
118 && is_not_backward_compatible
119 {
120 out.push(Warning::UnknownKey {
121 key: key.clone(),
122 profile: profile.clone(),
123 source: source.clone(),
124 });
125 }
126 }
127 }
128 }
129 }
130
131 Ok(out)
132 }
133}
134
135impl<P: Provider> Provider for WarningsProvider<P> {
136 fn metadata(&self) -> Metadata {
137 if let Some(source) = self.provider.metadata().source {
138 Metadata::from("Warnings", source)
139 } else {
140 Metadata::named("Warnings")
141 }
142 }
143
144 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
145 let warnings = self.collect_warnings()?;
146 Ok(Map::from([(
147 self.profile.clone(),
148 Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
149 )]))
150 }
151
152 fn profile(&self) -> Option<Profile> {
153 Some(self.profile.clone())
154 }
155}