Skip to main content

cargo/config/
config_store.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context as _, bail};
5
6use super::{ConfigKey, ConfigValue, Definition, DefinitionKind};
7use crate::util::data_structures::HashMap;
8use crate::util::errors::CargoResult;
9
10use ConfigValue as CV;
11
12#[derive(Default, Debug)]
13pub struct ConfigStore {
14    /// A collection of configuration options
15    values: HashMap<String, ConfigValue>,
16    def: Definition,
17}
18
19impl ConfigStore {
20    pub fn with_values(values: HashMap<String, ConfigValue>) -> Self {
21        Self {
22            values,
23            def: Definition::BuiltIn,
24        }
25    }
26
27    pub fn with_table_value(value: ConfigValue) -> Option<Self> {
28        match value {
29            CV::Table(values, def) => Some(Self { values, def }),
30            _ => None,
31        }
32    }
33
34    /// Loads configuration from a path without processing includes.
35    pub fn read_layer(path: &Path, definition_kind: DefinitionKind) -> CargoResult<Self> {
36        tracing::debug!(?path, ?definition_kind, "load config from file");
37
38        let contents = fs::read_to_string(path)
39            .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
40        let toml = contents.parse::<toml::Table>().with_context(|| {
41            format!("could not parse TOML configuration in `{}`", path.display())
42        })?;
43        let def = match definition_kind {
44            DefinitionKind::Cli => Definition::Cli(Some(path.into())),
45            DefinitionKind::Path => Definition::Path(path.into()),
46            DefinitionKind::BuiltIn => Definition::BuiltIn,
47            DefinitionKind::Environment => {
48                unimplemented!("Environment defiinitions are not supported")
49            }
50        };
51        let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
52            format!(
53                "failed to load TOML configuration from `{}`",
54                path.display()
55            )
56        })?;
57        Ok(Self::with_table_value(value).unwrap())
58    }
59
60    pub fn into_value(self) -> ConfigValue {
61        let Self { values, def } = self;
62        CV::Table(values, def)
63    }
64
65    pub fn definition(&self) -> &Definition {
66        &self.def
67    }
68
69    /// Merge the given values into self.
70    ///
71    /// If `force` is true, primitive (non-container) types will override existing values
72    /// of equal priority. For arrays, incoming values of equal priority will be placed later.
73    ///
74    /// Container types (tables and arrays) are merged with existing values.
75    ///
76    /// Container and non-container types cannot be mixed.
77    pub fn merge(&mut self, from: Self, force: bool) -> CargoResult<()> {
78        let mut root_cv = std::mem::take(self).into_value();
79        let from = from.into_value();
80        root_cv.merge(from, force)?;
81        *self = Self::with_table_value(root_cv).unwrap();
82
83        Ok(())
84    }
85
86    pub fn values(&self) -> &HashMap<String, ConfigValue> {
87        &self.values
88    }
89
90    pub fn values_mut(&mut self) -> &mut HashMap<String, ConfigValue> {
91        &mut self.values
92    }
93
94    /// Get a configuration value by key.
95    ///
96    /// This does NOT look at environment variables. See `get_cv_with_env` for
97    /// a variant that supports environment variables.
98    pub fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
99        let vals = self.values();
100        tracing::trace!("get cv {:?}", key);
101        if key.is_root() {
102            // Returning the entire root table (for example `cargo config get`
103            // with no key). The definition here shouldn't matter.
104            return Ok(Some(CV::Table(
105                vals.clone(),
106                Definition::Path(PathBuf::new()),
107            )));
108        }
109        let mut parts = key.parts().enumerate();
110        let Some(mut val) = vals.get(parts.next().unwrap().1) else {
111            return Ok(None);
112        };
113        for (i, part) in parts {
114            match val {
115                CV::Table(map, _) => {
116                    val = match map.get(part) {
117                        Some(val) => val,
118                        None => return Ok(None),
119                    }
120                }
121                CV::Integer(_, def)
122                | CV::String(_, def)
123                | CV::List(_, def)
124                | CV::Boolean(_, def) => {
125                    let key_so_far = ConfigKey::from_parts(key.parts().take(i));
126                    bail!(
127                        "expected table for configuration key `{}`, \
128                         but found {} in {}",
129                        key_so_far,
130                        val.desc(),
131                        def
132                    )
133                }
134            }
135        }
136        Ok(Some(val.clone()))
137    }
138}