1use 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
21const 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#[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 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 if force || is_higher_priority {
162 mem::swap(new, old);
163 }
164 } else {
165 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 (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}