#
cargo/util/context/
config_value.rs

    1//! [`ConfigValue`] represents Cargo configuration values loaded from TOML.
    2//!
    3//! See [the module-level doc](crate::util::context)
    4//! for how configuration is parsed and deserialized.
    5
    6use 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
    23/// List of which configuration lists cannot be merged.
    24///
    25/// Instead of merging,
    26/// the higher priority list should replaces the lower priority list.
    27///
    28/// ## What kind of config is non-mergeable
    29///
    30/// The rule of thumb is that if a config is a path of a program,
    31/// it should be added to this list.
    32const 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/// Similar to [`toml::Value`] but includes the source location where it is defined.
    42#[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    /// Merge the given value into self.
    141    ///
    142    /// If `force` is true, primitive (non-container) types will override existing values
    143    /// of equal priority. For arrays, incoming values of equal priority will be placed later.
    144    ///
    145    /// Container types (tables and arrays) are merged with existing values.
    146    ///
    147    /// Container and non-container types cannot be mixed.
    148    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                    // Use whichever list is higher priority.
    163                    if force || is_higher_priority {
    164                        mem::swap(new, old);
    165                    }
    166                } else {
    167                    // Merge the lists together.
    168                    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            // Allow switching types except for tables or arrays.
    201            (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}