cargo/util/context/
value.rs

1//! Deserialization of a [`Value<T>`] type which tracks where it was deserialized from.
2//!
3//! ## Rationale for `Value<T>`
4//!
5//! Often Cargo wants to report semantic error information or other sorts of
6//! error information about configuration keys but it also may wish to indicate
7//! as an error context where the key was defined as well (to help user
8//! debugging). The `Value<T>` type here can be used to deserialize a `T` value
9//! from configuration, but also record where it was deserialized from when it
10//! was read.
11//!
12//! Deserializing `Value<T>` is pretty special, and serde doesn't have built-in
13//! support for this operation. To implement this we extend serde's "data model"
14//! a bit. We configure deserialization of `Value<T>` to basically only work with
15//! our one deserializer using configuration.
16//!
17//! ## How `Value<T>` deserialization works
18//!
19//! `Value<T>` uses a custom protocol to inject source location information
20//! into serde's deserialization process:
21//!
22//! **Magic identifiers**: `Value<T>::deserialize` requests a struct with special
23//! [name](NAME) and [field names](FIELDS) that use invalid Rust syntax to avoid
24//! conflicts. This signals to Cargo's deserializer that location tracking is needed.
25//!
26//! **Custom deserializer response**: When Cargo's deserializer sees these magic
27//! identifiers, it switches to `ValueDeserializer` (from the [`de`] module)
28//! instead of normal struct deserialization.
29//!
30//! **Two-field protocol**: `ValueDeserializer` presents exactly two fields
31//! through map visiting:
32//! * The actual value (deserialized normally)
33//! * The definition context (encoded as a `(u32, String)` tuple acting as a
34//!   tagged union of [`Definition`] variants)
35//!
36//! This allows `Value<T>` to capture both the deserialized data and where it
37//! came from.
38//!
39//! **Note**: When modifying [`Definition`] variants, be sure to update both
40//! the `Definition::deserialize` implementation here and the
41//! `MapAccess::next_value_seed` implementation in `ValueDeserializer`.
42//!
43//! [`de`]: crate::util::context::de
44
45use crate::util::context::GlobalContext;
46use serde::de;
47use std::cmp::Ordering;
48use std::fmt;
49use std::marker;
50use std::mem;
51use std::path::{Path, PathBuf};
52
53/// A type which can be deserialized as a configuration value which records
54/// where it was deserialized from.
55#[derive(Debug, PartialEq, Clone)]
56pub struct Value<T> {
57    /// The inner value that was deserialized.
58    pub val: T,
59    /// The location where `val` was defined in configuration (e.g. file it was
60    /// defined in, env var etc).
61    pub definition: Definition,
62}
63
64pub type OptValue<T> = Option<Value<T>>;
65
66// The names below are intended to be invalid Rust identifiers
67// to avoid conflicts with other valid structures.
68pub(crate) const VALUE_FIELD: &str = "$__cargo_private_value";
69pub(crate) const DEFINITION_FIELD: &str = "$__cargo_private_definition";
70pub(crate) const NAME: &str = "$__cargo_private_Value";
71pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD];
72
73/// Location where a config value is defined.
74#[derive(Clone, Debug, Eq)]
75pub enum Definition {
76    BuiltIn,
77    /// Defined in a `.cargo/config`, includes the path to the file.
78    Path(PathBuf),
79    /// Defined in an environment variable, includes the environment key.
80    Environment(String),
81    /// Passed in on the command line.
82    /// A path is attached when the config value is a path to a config file.
83    Cli(Option<PathBuf>),
84}
85
86impl PartialOrd for Definition {
87    fn partial_cmp(&self, other: &Definition) -> Option<Ordering> {
88        Some(self.cmp(other))
89    }
90}
91
92impl Ord for Definition {
93    fn cmp(&self, other: &Definition) -> Ordering {
94        if mem::discriminant(self) == mem::discriminant(other) {
95            Ordering::Equal
96        } else if self.is_higher_priority(other) {
97            Ordering::Greater
98        } else {
99            Ordering::Less
100        }
101    }
102}
103
104impl Definition {
105    /// Root directory where this is defined.
106    ///
107    /// If from a file, it is the directory above `.cargo/config`.
108    /// CLI and env are the current working directory.
109    pub fn root<'a>(&'a self, gctx: &'a GlobalContext) -> &'a Path {
110        match self {
111            Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().parent().unwrap(),
112            Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
113        }
114    }
115
116    /// Returns true if self is a higher priority to other.
117    ///
118    /// CLI is preferred over environment, which is preferred over files.
119    pub fn is_higher_priority(&self, other: &Definition) -> bool {
120        matches!(
121            (self, other),
122            (Definition::Cli(_), Definition::Environment(_))
123                | (Definition::Cli(_), Definition::Path(_))
124                | (Definition::Cli(_), Definition::BuiltIn)
125                | (Definition::Environment(_), Definition::Path(_))
126                | (Definition::Environment(_), Definition::BuiltIn)
127                | (Definition::Path(_), Definition::BuiltIn)
128        )
129    }
130}
131
132impl PartialEq for Definition {
133    fn eq(&self, other: &Definition) -> bool {
134        // configuration values are equivalent no matter where they're defined,
135        // but they need to be defined in the same location. For example if
136        // they're defined in the environment that's different than being
137        // defined in a file due to path interpretations.
138        mem::discriminant(self) == mem::discriminant(other)
139    }
140}
141
142impl fmt::Display for Definition {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            Definition::Path(p) | Definition::Cli(Some(p)) => p.display().fmt(f),
146            Definition::Environment(key) => write!(f, "environment variable `{}`", key),
147            Definition::Cli(None) => write!(f, "--config cli option"),
148            Definition::BuiltIn => write!(f, "default"),
149        }
150    }
151}
152
153impl<T> From<T> for Value<T> {
154    fn from(val: T) -> Self {
155        Self {
156            val,
157            definition: Definition::BuiltIn,
158        }
159    }
160}
161
162impl<'de, T> de::Deserialize<'de> for Value<T>
163where
164    T: de::Deserialize<'de>,
165{
166    fn deserialize<D>(deserializer: D) -> Result<Value<T>, D::Error>
167    where
168        D: de::Deserializer<'de>,
169    {
170        struct ValueVisitor<T> {
171            _marker: marker::PhantomData<T>,
172        }
173
174        impl<'de, T> de::Visitor<'de> for ValueVisitor<T>
175        where
176            T: de::Deserialize<'de>,
177        {
178            type Value = Value<T>;
179
180            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
181                formatter.write_str("a value")
182            }
183
184            fn visit_map<V>(self, mut visitor: V) -> Result<Value<T>, V::Error>
185            where
186                V: de::MapAccess<'de>,
187            {
188                let value = visitor.next_key::<ValueKey>()?;
189                if value.is_none() {
190                    return Err(de::Error::custom("value not found"));
191                }
192                let val: T = visitor.next_value()?;
193
194                let definition = visitor.next_key::<DefinitionKey>()?;
195                if definition.is_none() {
196                    return Err(de::Error::custom("definition not found"));
197                }
198                let definition: Definition = visitor.next_value()?;
199                Ok(Value { val, definition })
200            }
201        }
202
203        deserializer.deserialize_struct(
204            NAME,
205            &FIELDS,
206            ValueVisitor {
207                _marker: marker::PhantomData,
208            },
209        )
210    }
211}
212
213struct FieldVisitor {
214    expected: &'static str,
215}
216
217impl<'de> de::Visitor<'de> for FieldVisitor {
218    type Value = ();
219
220    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
221        formatter.write_str("a valid value field")
222    }
223
224    fn visit_str<E>(self, s: &str) -> Result<(), E>
225    where
226        E: de::Error,
227    {
228        if s == self.expected {
229            Ok(())
230        } else {
231            Err(de::Error::custom("expected field with custom name"))
232        }
233    }
234}
235
236struct ValueKey;
237
238impl<'de> de::Deserialize<'de> for ValueKey {
239    fn deserialize<D>(deserializer: D) -> Result<ValueKey, D::Error>
240    where
241        D: de::Deserializer<'de>,
242    {
243        deserializer.deserialize_identifier(FieldVisitor {
244            expected: VALUE_FIELD,
245        })?;
246        Ok(ValueKey)
247    }
248}
249
250struct DefinitionKey;
251
252impl<'de> de::Deserialize<'de> for DefinitionKey {
253    fn deserialize<D>(deserializer: D) -> Result<DefinitionKey, D::Error>
254    where
255        D: de::Deserializer<'de>,
256    {
257        deserializer.deserialize_identifier(FieldVisitor {
258            expected: DEFINITION_FIELD,
259        })?;
260        Ok(DefinitionKey)
261    }
262}
263
264impl<'de> de::Deserialize<'de> for Definition {
265    fn deserialize<D>(deserializer: D) -> Result<Definition, D::Error>
266    where
267        D: de::Deserializer<'de>,
268    {
269        let (discr, value) = <(u32, String)>::deserialize(deserializer)?;
270        match discr {
271            0 => Ok(Definition::BuiltIn),
272            1 => Ok(Definition::Path(value.into())),
273            2 => Ok(Definition::Environment(value)),
274            3 => {
275                let path = (value.len() > 0).then_some(value.into());
276                Ok(Definition::Cli(path))
277            }
278            _ => panic!("unexpected discriminant {discr} value {value}"),
279        }
280    }
281}