#
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 serde::de;
    46use std::cmp::Ordering;
    47use std::fmt;
    48use std::marker;
    49use std::mem;
    50use std::path::{Path, PathBuf};
    51
    52/// A type which can be deserialized as a configuration value which records
    53/// where it was deserialized from.
    54#[derive(Debug, PartialEq, Clone)]
    55pub struct Value<T> {
    56    /// The inner value that was deserialized.
    57    pub val: T,
    58    /// The location where `val` was defined in configuration (e.g. file it was
    59    /// defined in, env var etc).
    60    pub definition: Definition,
    61}
    62
    63pub type OptValue<T> = Option<Value<T>>;
    64
    65// The names below are intended to be invalid Rust identifiers
    66// to avoid conflicts with other valid structures.
    67pub(crate) const VALUE_FIELD: &str = "$__cargo_private_value";
    68pub(crate) const DEFINITION_FIELD: &str = "$__cargo_private_definition";
    69pub(crate) const NAME: &str = "$__cargo_private_Value";
    70pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD];
    71
    72/// Location where a config value is defined.
    73#[derive(Clone, Debug, Eq)]
    74pub enum Definition {
    75    BuiltIn,
    76    /// Defined in a `.cargo/config`, includes the path to the file.
    77    Path(PathBuf),
    78    /// Defined in an environment variable, includes the environment key.
    79    Environment(String),
    80    /// Passed in on the command line.
    81    /// A path is attached when the config value is a path to a config file.
    82    Cli(Option<PathBuf>),
    83}
    84
    85impl PartialOrd for Definition {
    86    fn partial_cmp(&self, other: &Definition) -> Option<Ordering> {
    87        Some(self.cmp(other))
    88    }
    89}
    90
    91impl Ord for Definition {
    92    fn cmp(&self, other: &Definition) -> Ordering {
    93        if mem::discriminant(self) == mem::discriminant(other) {
    94            Ordering::Equal
    95        } else if self.is_higher_priority(other) {
    96            Ordering::Greater
    97        } else {
    98            Ordering::Less
    99        }
    100    }
    101}
    102
    103impl Definition {
    104    /// Root directory where this is defined.
    105    ///
    106    /// If from a file, it is the directory above `.cargo/config.toml`.
    107    /// CLI and env use the provided current working directory.
    108    pub fn root<'a>(&'a self, cwd: &'a Path) -> &'a Path {
    109        match self {
    110            Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().parent().unwrap(),
    111            Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => cwd,
    112        }
    113    }
    114
    115    /// Returns true if self is a higher priority to other.
    116    ///
    117    /// CLI is preferred over environment, which is preferred over files.
    118    pub fn is_higher_priority(&self, other: &Definition) -> bool {
    119        matches!(
    120            (self, other),
    121            (Definition::Cli(_), Definition::Environment(_))
    122                | (Definition::Cli(_), Definition::Path(_))
    123                | (Definition::Cli(_), Definition::BuiltIn)
    124                | (Definition::Environment(_), Definition::Path(_))
    125                | (Definition::Environment(_), Definition::BuiltIn)
    126                | (Definition::Path(_), Definition::BuiltIn)
    127        )
    128    }
    129}
    130
    131impl PartialEq for Definition {
    132    fn eq(&self, other: &Definition) -> bool {
    133        // configuration values are equivalent no matter where they're defined,
    134        // but they need to be defined in the same location. For example if
    135        // they're defined in the environment that's different than being
    136        // defined in a file due to path interpretations.
    137        mem::discriminant(self) == mem::discriminant(other)
    138    }
    139}
    140
    141impl fmt::Display for Definition {
    142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    143        match self {
    144            Definition::Path(p) | Definition::Cli(Some(p)) => p.display().fmt(f),
    145            Definition::Environment(key) => write!(f, "environment variable `{}`", key),
    146            Definition::Cli(None) => write!(f, "--config cli option"),
    147            Definition::BuiltIn => write!(f, "default"),
    148        }
    149    }
    150}
    151
    152impl<T> From<T> for Value<T> {
    153    fn from(val: T) -> Self {
    154        Self {
    155            val,
    156            definition: Definition::BuiltIn,
    157        }
    158    }
    159}
    160
    161impl<'de, T> de::Deserialize<'de> for Value<T>
    162where
    163    T: de::Deserialize<'de>,
    164{
    165    fn deserialize<D>(deserializer: D) -> Result<Value<T>, D::Error>
    166    where
    167        D: de::Deserializer<'de>,
    168    {
    169        struct ValueVisitor<T> {
    170            _marker: marker::PhantomData<T>,
    171        }
    172
    173        impl<'de, T> de::Visitor<'de> for ValueVisitor<T>
    174        where
    175            T: de::Deserialize<'de>,
    176        {
    177            type Value = Value<T>;
    178
    179            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    180                formatter.write_str("a value")
    181            }
    182
    183            fn visit_map<V>(self, mut visitor: V) -> Result<Value<T>, V::Error>
    184            where
    185                V: de::MapAccess<'de>,
    186            {
    187                let value = visitor.next_key::<ValueKey>()?;
    188                if value.is_none() {
    189                    return Err(de::Error::custom("value not found"));
    190                }
    191                let val: T = visitor.next_value()?;
    192
    193                let definition = visitor.next_key::<DefinitionKey>()?;
    194                if definition.is_none() {
    195                    return Err(de::Error::custom("definition not found"));
    196                }
    197                let definition: Definition = visitor.next_value()?;
    198                Ok(Value { val, definition })
    199            }
    200        }
    201
    202        deserializer.deserialize_struct(
    203            NAME,
    204            &FIELDS,
    205            ValueVisitor {
    206                _marker: marker::PhantomData,
    207            },
    208        )
    209    }
    210}
    211
    212struct FieldVisitor {
    213    expected: &'static str,
    214}
    215
    216impl<'de> de::Visitor<'de> for FieldVisitor {
    217    type Value = ();
    218
    219    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    220        formatter.write_str("a valid value field")
    221    }
    222
    223    fn visit_str<E>(self, s: &str) -> Result<(), E>
    224    where
    225        E: de::Error,
    226    {
    227        if s == self.expected {
    228            Ok(())
    229        } else {
    230            Err(de::Error::custom("expected field with custom name"))
    231        }
    232    }
    233}
    234
    235struct ValueKey;
    236
    237impl<'de> de::Deserialize<'de> for ValueKey {
    238    fn deserialize<D>(deserializer: D) -> Result<ValueKey, D::Error>
    239    where
    240        D: de::Deserializer<'de>,
    241    {
    242        deserializer.deserialize_identifier(FieldVisitor {
    243            expected: VALUE_FIELD,
    244        })?;
    245        Ok(ValueKey)
    246    }
    247}
    248
    249struct DefinitionKey;
    250
    251impl<'de> de::Deserialize<'de> for DefinitionKey {
    252    fn deserialize<D>(deserializer: D) -> Result<DefinitionKey, D::Error>
    253    where
    254        D: de::Deserializer<'de>,
    255    {
    256        deserializer.deserialize_identifier(FieldVisitor {
    257            expected: DEFINITION_FIELD,
    258        })?;
    259        Ok(DefinitionKey)
    260    }
    261}
    262
    263impl<'de> de::Deserialize<'de> for Definition {
    264    fn deserialize<D>(deserializer: D) -> Result<Definition, D::Error>
    265    where
    266        D: de::Deserializer<'de>,
    267    {
    268        let (discr, value) = <(u32, String)>::deserialize(deserializer)?;
    269        match discr {
    270            0 => Ok(Definition::BuiltIn),
    271            1 => Ok(Definition::Path(value.into())),
    272            2 => Ok(Definition::Environment(value)),
    273            3 => {
    274                let path = (value.len() > 0).then_some(value.into());
    275                Ok(Definition::Cli(path))
    276            }
    277            _ => panic!("unexpected discriminant {discr} value {value}"),
    278        }
    279    }
    280}