1use std::collections::HashMap;
7use std::collections::hash_map::Entry;
8use std::fmt;
9use std::mem;
10
11use anyhow::Context as _;
12use anyhow::anyhow;
13use anyhow::bail;
14
15use super::ConfigKey;
16use super::Definition;
17use super::key;
18use super::key::KeyOrIdx;
19use crate::CargoResult;
20
21use self::ConfigValue as CV;
22
23const NON_MERGEABLE_LISTS: &[&str] = &[
33 "credential-alias.*",
34 "doc.browser",
35 "host.runner",
36 "registries.*.credential-provider",
37 "registry.credential-provider",
38 "target.*.runner",
39];
40
41#[derive(Eq, PartialEq, Clone)]
43pub enum ConfigValue {
44 Integer(i64, Definition),
45 String(String, Definition),
46 List(Vec<ConfigValue>, Definition),
47 Table(HashMap<String, ConfigValue>, Definition),
48 Boolean(bool, Definition),
49}
50
51impl fmt::Debug for ConfigValue {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
55 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
56 CV::String(s, def) => write!(f, "{} (from {})", s, def),
57 CV::List(list, def) => {
58 write!(f, "[")?;
59 for (i, item) in list.iter().enumerate() {
60 if i > 0 {
61 write!(f, ", ")?;
62 }
63 write!(f, "{item:?}")?;
64 }
65 write!(f, "] (from {})", def)
66 }
67 CV::Table(table, _) => write!(f, "{:?}", table),
68 }
69 }
70}
71
72impl ConfigValue {
73 pub(super) fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
74 let mut error_path = Vec::new();
75 Self::from_toml_inner(def, toml, &mut error_path).with_context(|| {
76 let mut it = error_path.iter().rev().peekable();
77 let mut key_path = String::with_capacity(error_path.len() * 3);
78 while let Some(k) = it.next() {
79 match k {
80 KeyOrIdx::Key(s) => key_path.push_str(&key::escape_key_part(&s)),
81 KeyOrIdx::Idx(i) => key_path.push_str(&format!("[{i}]")),
82 }
83 if matches!(it.peek(), Some(KeyOrIdx::Key(_))) {
84 key_path.push('.');
85 }
86 }
87 format!("failed to parse config at `{key_path}`")
88 })
89 }
90
91 fn from_toml_inner(
92 def: Definition,
93 toml: toml::Value,
94 path: &mut Vec<KeyOrIdx>,
95 ) -> CargoResult<ConfigValue> {
96 match toml {
97 toml::Value::String(val) => Ok(CV::String(val, def)),
98 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
99 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
100 toml::Value::Array(val) => Ok(CV::List(
101 val.into_iter()
102 .enumerate()
103 .map(|(i, toml)| {
104 CV::from_toml_inner(def.clone(), toml, path)
105 .inspect_err(|_| path.push(KeyOrIdx::Idx(i)))
106 })
107 .collect::<CargoResult<_>>()?,
108 def,
109 )),
110 toml::Value::Table(val) => Ok(CV::Table(
111 val.into_iter()
112 .map(
113 |(key, value)| match CV::from_toml_inner(def.clone(), value, path) {
114 Ok(value) => Ok((key, value)),
115 Err(e) => {
116 path.push(KeyOrIdx::Key(key));
117 Err(e)
118 }
119 },
120 )
121 .collect::<CargoResult<_>>()?,
122 def,
123 )),
124 v => bail!("unsupported TOML configuration type `{}`", v.type_str()),
125 }
126 }
127
128 pub(super) fn into_toml(self) -> toml::Value {
129 match self {
130 CV::Boolean(s, _) => toml::Value::Boolean(s),
131 CV::String(s, _) => toml::Value::String(s),
132 CV::Integer(i, _) => toml::Value::Integer(i),
133 CV::List(l, _) => toml::Value::Array(l.into_iter().map(|cv| cv.into_toml()).collect()),
134 CV::Table(l, _) => {
135 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
136 }
137 }
138 }
139
140 pub(super) fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
149 self.merge_helper(from, force, &mut ConfigKey::new())
150 }
151
152 fn merge_helper(
153 &mut self,
154 from: ConfigValue,
155 force: bool,
156 parts: &mut ConfigKey,
157 ) -> CargoResult<()> {
158 let is_higher_priority = from.definition().is_higher_priority(self.definition());
159 match (self, from) {
160 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
161 if is_nonmergeable_list(&parts) {
162 if force || is_higher_priority {
164 mem::swap(new, old);
165 }
166 } else {
167 if force {
169 old.append(new);
170 } else {
171 new.append(old);
172 mem::swap(new, old);
173 }
174 }
175 old.sort_by(|a, b| a.definition().cmp(b.definition()));
176 }
177 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
178 for (key, value) in mem::take(new) {
179 match old.entry(key.clone()) {
180 Entry::Occupied(mut entry) => {
181 let new_def = value.definition().clone();
182 let entry = entry.get_mut();
183 parts.push(&key);
184 entry.merge_helper(value, force, parts).with_context(|| {
185 format!(
186 "failed to merge key `{}` between \
187 {} and {}",
188 key,
189 entry.definition(),
190 new_def,
191 )
192 })?;
193 }
194 Entry::Vacant(entry) => {
195 entry.insert(value);
196 }
197 };
198 }
199 }
200 (expected @ &mut CV::List(_, _), found)
202 | (expected @ &mut CV::Table(_, _), found)
203 | (expected, found @ CV::List(_, _))
204 | (expected, found @ CV::Table(_, _)) => {
205 return Err(anyhow!(
206 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
207 found.definition(),
208 expected.definition(),
209 expected.desc(),
210 found.desc()
211 ));
212 }
213 (old, mut new) => {
214 if force || is_higher_priority {
215 mem::swap(old, &mut new);
216 }
217 }
218 }
219
220 Ok(())
221 }
222
223 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
224 match self {
225 CV::Integer(i, def) => Ok((*i, def)),
226 _ => self.expected("integer", key),
227 }
228 }
229
230 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
231 match self {
232 CV::String(s, def) => Ok((s, def)),
233 _ => self.expected("string", key),
234 }
235 }
236
237 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
238 match self {
239 CV::Table(table, def) => Ok((table, def)),
240 _ => self.expected("table", key),
241 }
242 }
243
244 pub fn table_mut(
245 &mut self,
246 key: &str,
247 ) -> CargoResult<(&mut HashMap<String, ConfigValue>, &mut Definition)> {
248 match self {
249 CV::Table(table, def) => Ok((table, def)),
250 _ => self.expected("table", key),
251 }
252 }
253
254 pub fn is_table(&self) -> bool {
255 matches!(self, CV::Table(..))
256 }
257
258 pub fn string_list(&self, key: &str) -> CargoResult<Vec<(String, Definition)>> {
259 match self {
260 CV::List(list, _) => list
261 .iter()
262 .map(|cv| match cv {
263 CV::String(s, def) => Ok((s.clone(), def.clone())),
264 _ => self.expected("string", key),
265 })
266 .collect::<CargoResult<_>>(),
267 _ => self.expected("list", key),
268 }
269 }
270
271 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
272 match self {
273 CV::Boolean(b, def) => Ok((*b, def)),
274 _ => self.expected("bool", key),
275 }
276 }
277
278 pub fn desc(&self) -> &'static str {
279 match *self {
280 CV::Table(..) => "table",
281 CV::List(..) => "array",
282 CV::String(..) => "string",
283 CV::Boolean(..) => "boolean",
284 CV::Integer(..) => "integer",
285 }
286 }
287
288 pub fn definition(&self) -> &Definition {
289 match self {
290 CV::Boolean(_, def)
291 | CV::Integer(_, def)
292 | CV::String(_, def)
293 | CV::List(_, def)
294 | CV::Table(_, def) => def,
295 }
296 }
297
298 pub(super) fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
299 bail!(
300 "expected a {}, but found a {} for `{}` in {}",
301 wanted,
302 self.desc(),
303 key,
304 self.definition()
305 )
306 }
307}
308
309pub(super) fn is_nonmergeable_list(key: &ConfigKey) -> bool {
310 NON_MERGEABLE_LISTS.iter().any(|l| key.matches(l))
311}