#
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}