cargo/util/context/
value.rs

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