Skip to main content

cargo/config/
config_value.rs

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