Skip to main content

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