foundry_config/inline/
mod.rs
1use crate::Config;
2use alloy_primitives::map::HashMap;
3use figment::{
4 value::{Dict, Map, Value},
5 Figment, Profile, Provider,
6};
7use foundry_compilers::ProjectCompileOutput;
8use itertools::Itertools;
9
10mod natspec;
11pub use natspec::*;
12
13const INLINE_CONFIG_PREFIX: &str = "forge-config:";
14
15type DataMap = Map<Profile, Dict>;
16
17#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
19pub enum InlineConfigErrorKind {
20 #[error(transparent)]
22 Parse(#[from] toml::de::Error),
23 #[error("invalid profile `{0}`; valid profiles: {1}")]
25 InvalidProfile(String, String),
26}
27
28#[derive(Debug, thiserror::Error)]
31#[error("Inline config error at {location}: {kind}")]
32pub struct InlineConfigError {
33 pub location: String,
36 pub kind: InlineConfigErrorKind,
38}
39
40#[derive(Clone, Debug, Default)]
44pub struct InlineConfig {
45 contract_level: HashMap<String, DataMap>,
47 fn_level: HashMap<(String, String), DataMap>,
49}
50
51impl InlineConfig {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn new_parsed(output: &ProjectCompileOutput, config: &Config) -> eyre::Result<Self> {
60 let natspecs: Vec<NatSpec> = NatSpec::parse(output, &config.root);
61 let profiles = &config.profiles;
62 let mut inline = Self::new();
63 for natspec in &natspecs {
64 inline.insert(natspec)?;
65 natspec.validate_profiles(profiles)?;
67 }
68 Ok(inline)
69 }
70
71 pub fn insert(&mut self, natspec: &NatSpec) -> Result<(), InlineConfigError> {
73 let map = if let Some(function) = &natspec.function {
74 self.fn_level.entry((natspec.contract.clone(), function.clone())).or_default()
75 } else {
76 self.contract_level.entry(natspec.contract.clone()).or_default()
77 };
78 let joined = natspec
79 .config_values()
80 .map(|s| {
81 if let Some(idx) = s.find('=') {
83 s[..idx].replace('-', "_") + &s[idx..]
84 } else {
85 s.to_string()
86 }
87 })
88 .format("\n")
89 .to_string();
90 let data = toml::from_str::<DataMap>(&joined).map_err(|e| InlineConfigError {
91 location: natspec.location_string(),
92 kind: InlineConfigErrorKind::Parse(e),
93 })?;
94 extend_data_map(map, &data);
95 Ok(())
96 }
97
98 pub fn provide<'a>(&'a self, contract: &'a str, function: &'a str) -> InlineConfigProvider<'a> {
101 InlineConfigProvider { inline: self, contract, function }
102 }
103
104 pub fn merge(&self, contract: &str, function: &str, base: &Config) -> Figment {
107 Figment::from(base).merge(self.provide(contract, function))
108 }
109
110 pub fn contains_contract(&self, contract: &str) -> bool {
112 self.get_contract(contract).is_some_and(|map| !map.is_empty())
113 }
114
115 pub fn contains_function(&self, contract: &str, function: &str) -> bool {
119 self.get_function(contract, function).is_some_and(|map| !map.is_empty())
120 }
121
122 fn get_contract(&self, contract: &str) -> Option<&DataMap> {
123 self.contract_level.get(contract)
124 }
125
126 fn get_function(&self, contract: &str, function: &str) -> Option<&DataMap> {
127 let key = (contract.to_string(), function.to_string());
128 self.fn_level.get(&key)
129 }
130}
131
132#[derive(Clone, Debug)]
136pub struct InlineConfigProvider<'a> {
137 inline: &'a InlineConfig,
138 contract: &'a str,
139 function: &'a str,
140}
141
142impl Provider for InlineConfigProvider<'_> {
143 fn metadata(&self) -> figment::Metadata {
144 figment::Metadata::named("inline config")
145 }
146
147 fn data(&self) -> figment::Result<DataMap> {
148 let mut map = DataMap::new();
149 if let Some(new) = self.inline.get_contract(self.contract) {
150 extend_data_map(&mut map, new);
151 }
152 if let Some(new) = self.inline.get_function(self.contract, self.function) {
153 extend_data_map(&mut map, new);
154 }
155 Ok(map)
156 }
157}
158
159fn extend_data_map(map: &mut DataMap, new: &DataMap) {
160 for (profile, data) in new {
161 extend_dict(map.entry(profile.clone()).or_default(), data);
162 }
163}
164
165fn extend_dict(dict: &mut Dict, new: &Dict) {
166 for (k, v) in new {
167 match dict.entry(k.clone()) {
168 std::collections::btree_map::Entry::Vacant(entry) => {
169 entry.insert(v.clone());
170 }
171 std::collections::btree_map::Entry::Occupied(entry) => {
172 extend_value(entry.into_mut(), v);
173 }
174 }
175 }
176}
177
178fn extend_value(value: &mut Value, new: &Value) {
179 match (value, new) {
180 (Value::Dict(tag, dict), Value::Dict(new_tag, new_dict)) => {
181 *tag = *new_tag;
182 extend_dict(dict, new_dict);
183 }
184 (value, new) => *value = new.clone(),
185 }
186}