diff --git a/src/cargo/util/config/de.rs b/src/cargo/util/config/de.rs index aa0c92c8c..42a9f55a1 100644 --- a/src/cargo/util/config/de.rs +++ b/src/cargo/util/config/de.rs @@ -1,14 +1,16 @@ //! Support for deserializing configuration via `serde` -use crate::util::config::{Config, ConfigError, ConfigKey, ConfigKeyPart}; -use crate::util::config::{ConfigValue as CV, Value, Definition}; -use std::path::PathBuf; +use crate::util::config::value; +use crate::util::config::{Config, ConfigError, ConfigKey}; +use crate::util::config::{ConfigValue as CV, Definition, Value}; use serde::{de, de::IntoDeserializer}; use std::collections::HashSet; +use std::path::PathBuf; use std::vec; /// Serde deserializer used to convert config values to a target type using /// `Config::get`. +#[derive(Clone)] pub(crate) struct Deserializer<'config> { pub(crate) config: &'config Config, pub(crate) key: ConfigKey, @@ -21,10 +23,10 @@ macro_rules! deserialize_method { V: de::Visitor<'de>, { let v = self.config.$getter(&self.key)?.ok_or_else(|| - ConfigError::missing(&self.key.to_config()))?; + ConfigError::missing(&self.key))?; let Value{val, definition} = v; let res: Result = visitor.$visit(val); - res.map_err(|e| e.with_key_context(&self.key.to_config(), definition)) + res.map_err(|e| e.with_key_context(&self.key, definition)) } } } @@ -39,7 +41,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { // Future note: If you ever need to deserialize a non-self describing // map type, this should implement a starts_with check (similar to how // ConfigMapAccess does). - if let Some(v) = self.config.env.get(&self.key.to_env()) { + if let Some(v) = self.config.env.get(self.key.as_env_key()) { let res: Result = if v == "true" || v == "false" { visitor.visit_bool(v.parse().unwrap()) } else if let Ok(v) = v.parse::() { @@ -48,38 +50,34 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { && v.starts_with('[') && v.ends_with(']') { - visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) + visitor.visit_seq(ConfigSeqAccess::new(self.clone())?) } else { visitor.visit_string(v.clone()) }; return res.map_err(|e| { e.with_key_context( - &self.key.to_config(), - Definition::Environment(self.key.to_env()), + &self.key, + Definition::Environment(self.key.as_env_key().to_string()), ) }); } - let o_cv = self.config.get_cv(&self.key.to_config())?; + let o_cv = self.config.get_cv(self.key.as_config_key())?; if let Some(cv) = o_cv { let res: (Result, PathBuf) = match cv { CV::Integer(i, path) => (visitor.visit_i64(i), path), CV::String(s, path) => (visitor.visit_string(s), path), - CV::List(_, path) => ( - visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?), - path, - ), + CV::List(_, path) => (visitor.visit_seq(ConfigSeqAccess::new(self.clone())?), path), CV::Table(_, path) => ( - visitor.visit_map(ConfigMapAccess::new_map(self.config, self.key.clone())?), + visitor.visit_map(ConfigMapAccess::new_map(self.clone())?), path, ), CV::Boolean(b, path) => (visitor.visit_bool(b), path), }; let (res, path) = res; - return res - .map_err(|e| e.with_key_context(&self.key.to_config(), Definition::Path(path))); + return res.map_err(|e| e.with_key_context(&self.key, Definition::Path(path))); } - Err(ConfigError::missing(&self.key.to_config())) + Err(ConfigError::missing(&self.key)) } deserialize_method!(deserialize_bool, visit_bool, get_bool_priv); @@ -107,35 +105,41 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { fn deserialize_struct( self, - _name: &'static str, + name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { - visitor.visit_map(ConfigMapAccess::new_struct(self.config, self.key, fields)?) + if name == value::NAME && fields == value::FIELDS { + return visitor.visit_map(ValueDeserializer { + hits: 0, + deserializer: self, + }); + } + visitor.visit_map(ConfigMapAccess::new_struct(self, fields)?) } fn deserialize_map(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_map(ConfigMapAccess::new_map(self.config, self.key)?) + visitor.visit_map(ConfigMapAccess::new_map(self)?) } fn deserialize_seq(self, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) + visitor.visit_seq(ConfigSeqAccess::new(self)?) } fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { - visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) + visitor.visit_seq(ConfigSeqAccess::new(self)?) } fn deserialize_tuple_struct( @@ -147,7 +151,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { where V: de::Visitor<'de>, { - visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?) + visitor.visit_seq(ConfigSeqAccess::new(self)?) } fn deserialize_newtype_struct( @@ -169,7 +173,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { .to_string(); visitor.visit_newtype_struct(path.into_deserializer()) } - None => Err(ConfigError::missing(&self.key.to_config())), + None => Err(ConfigError::missing(&self.key)), } } else { visitor.visit_newtype_struct(self) @@ -185,70 +189,76 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { } struct ConfigMapAccess<'config> { - config: &'config Config, - key: ConfigKey, - set_iter: as IntoIterator>::IntoIter, - next: Option, + de: Deserializer<'config>, + set_iter: as IntoIterator>::IntoIter, + next: Option, +} + +#[derive(PartialEq, Eq, Hash)] +enum KeyKind { + Normal(String), + CaseSensitive(String), } impl<'config> ConfigMapAccess<'config> { - fn new_map( - config: &'config Config, - key: ConfigKey, - ) -> Result, ConfigError> { + fn new_map(de: Deserializer<'config>) -> Result, ConfigError> { let mut set = HashSet::new(); - if let Some(mut v) = config.get_table(&key.to_config())? { + if let Some(mut v) = de.config.get_table(de.key.as_config_key())? { // `v: Value>` for (key, _value) in v.val.drain() { - set.insert(ConfigKeyPart::CasePart(key)); + set.insert(KeyKind::CaseSensitive(key)); } } - if config.cli_unstable().advanced_env { + if de.config.cli_unstable().advanced_env { // `CARGO_PROFILE_DEV_OVERRIDES_` - let env_pattern = format!("{}_", key.to_env()); - for env_key in config.env.keys() { + let env_pattern = format!("{}_", de.key.as_env_key()); + for env_key in de.config.env.keys() { if env_key.starts_with(&env_pattern) { // `CARGO_PROFILE_DEV_OVERRIDES_bar_OPT_LEVEL = 3` let rest = &env_key[env_pattern.len()..]; // `rest = bar_OPT_LEVEL` let part = rest.splitn(2, '_').next().unwrap(); // `part = "bar"` - set.insert(ConfigKeyPart::CasePart(part.to_string())); + set.insert(KeyKind::CaseSensitive(part.to_string())); } } } Ok(ConfigMapAccess { - config, - key, + de, set_iter: set.into_iter(), next: None, }) } fn new_struct( - config: &'config Config, - key: ConfigKey, + de: Deserializer<'config>, fields: &'static [&'static str], ) -> Result, ConfigError> { let mut set = HashSet::new(); for field in fields { - set.insert(ConfigKeyPart::Part(field.to_string())); + set.insert(KeyKind::Normal(field.to_string())); } - if let Some(mut v) = config.get_table(&key.to_config())? { + + // Assume that if we're deserializing a struct it exhaustively lists all + // possible fields on this key that we're *supposed* to use, so take + // this opportunity to warn about any keys that aren't recognized as + // fields and warn about them. + if let Some(mut v) = de.config.get_table(de.key.as_config_key())? { for (t_key, value) in v.val.drain() { - let part = ConfigKeyPart::Part(t_key); - if !set.contains(&part) { - config.shell().warn(format!( - "unused key `{}` in config file `{}`", - key.join(part).to_config(), - value.definition_path().display() - ))?; + if set.contains(&KeyKind::Normal(t_key.to_string())) { + continue; } + de.config.shell().warn(format!( + "unused key `{}.{}` in config file `{}`", + de.key.as_config_key(), + t_key, + value.definition_path().display() + ))?; } } + Ok(ConfigMapAccess { - config, - key, + de, set_iter: set.into_iter(), next: None, }) @@ -264,9 +274,12 @@ impl<'de, 'config> de::MapAccess<'de> for ConfigMapAccess<'config> { { match self.set_iter.next() { Some(key) => { - let de_key = key.to_config(); + let name = match &key { + KeyKind::Normal(s) | KeyKind::CaseSensitive(s) => s.as_str(), + }; + let result = seed.deserialize(name.into_deserializer()).map(Some); self.next = Some(key); - seed.deserialize(de_key.into_deserializer()).map(Some) + return result; } None => Ok(None), } @@ -276,12 +289,16 @@ impl<'de, 'config> de::MapAccess<'de> for ConfigMapAccess<'config> { where V: de::DeserializeSeed<'de>, { - let next_key = self.next.take().expect("next field missing"); - let next_key = self.key.join(next_key); - seed.deserialize(Deserializer { - config: self.config, - key: next_key, - }) + match self.next.take().expect("next field missing") { + KeyKind::Normal(key) => self.de.key.push(&key), + KeyKind::CaseSensitive(key) => self.de.key.push_sensitive(&key), + } + let result = seed.deserialize(Deserializer { + config: self.de.config, + key: self.de.key.clone(), + }); + self.de.key.pop(); + return result; } } @@ -290,34 +307,32 @@ struct ConfigSeqAccess { } impl ConfigSeqAccess { - fn new(config: &Config, key: &ConfigKey) -> Result { + fn new(de: Deserializer<'_>) -> Result { let mut res = Vec::new(); - if let Some(v) = config.get_list(&key.to_config())? { + if let Some(v) = de.config.get_list(de.key.as_config_key())? { for (s, path) in v.val { res.push((s, Definition::Path(path))); } } - if config.cli_unstable().advanced_env { + if de.config.cli_unstable().advanced_env { // Parse an environment string as a TOML array. - let env_key = key.to_env(); - let def = Definition::Environment(env_key.clone()); - if let Some(v) = config.env.get(&env_key) { + if let Some(v) = de.config.env.get(de.key.as_env_key()) { + let def = Definition::Environment(de.key.as_env_key().to_string()); if !(v.starts_with('[') && v.ends_with(']')) { return Err(ConfigError::new( format!("should have TOML list syntax, found `{}`", v), def, )); } - let temp_key = key.last().to_env(); - let toml_s = format!("{}={}", temp_key, v); + let toml_s = format!("value={}", v); let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| { ConfigError::new(format!("could not parse TOML list: {}", e), def.clone()) })?; let values = toml_v .as_table() .unwrap() - .get(&temp_key) + .get("value") .unwrap() .as_array() .expect("env var was not array"); @@ -353,3 +368,47 @@ impl<'de> de::SeqAccess<'de> for ConfigSeqAccess { } } } + +struct ValueDeserializer<'config> { + hits: u32, + deserializer: Deserializer<'config>, +} + +impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> { + type Error = ConfigError; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: de::DeserializeSeed<'de>, + { + self.hits += 1; + match self.hits { + 1 => seed + .deserialize(value::VALUE_FIELD.into_deserializer()) + .map(Some), + 2 => seed + .deserialize(value::DEFINITION_FIELD.into_deserializer()) + .map(Some), + _ => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + if self.hits == 1 { + seed.deserialize(Deserializer { + config: self.deserializer.config, + key: self.deserializer.key.clone(), + }) + } else { + // let env = self.deserializer.key.to_env(); + // if self.deserializer.config.env.contains_key(&env) { + // } else { + // } + // if let someself.deserializer.config.get_env(&self.deserializer.key) + panic!() + } + } +} diff --git a/src/cargo/util/config/key.rs b/src/cargo/util/config/key.rs new file mode 100644 index 000000000..2a3716262 --- /dev/null +++ b/src/cargo/util/config/key.rs @@ -0,0 +1,68 @@ +use std::fmt; + +/// Key for a configuration variable. +#[derive(Debug, Clone)] +pub struct ConfigKey { + env: String, + config: String, + parts: Vec<(usize, usize)>, +} + +impl ConfigKey { + pub fn new() -> ConfigKey { + ConfigKey { + env: "CARGO".to_string(), + config: String::new(), + parts: Vec::new(), + } + } + + pub fn from_str(key: &str) -> ConfigKey { + let mut cfg = ConfigKey::new(); + for part in key.split('.') { + cfg.push(part); + } + return cfg; + } + + pub fn push(&mut self, name: &str) { + let env = name.replace("-", "_").to_uppercase(); + self._push(&env, name); + } + + pub fn push_sensitive(&mut self, name: &str) { + self._push(name, name); + } + + fn _push(&mut self, env: &str, config: &str) { + self.parts.push((self.env.len(), self.config.len())); + + self.env.push_str("_"); + self.env.push_str(env); + + if !self.config.is_empty() { + self.config.push_str("."); + } + self.config.push_str(config); + } + + pub fn pop(&mut self) { + let (env, config) = self.parts.pop().unwrap(); + self.env.truncate(env); + self.config.truncate(config); + } + + pub fn as_env_key(&self) -> &str { + &self.env + } + + pub fn as_config_key(&self) -> &str { + &self.config + } +} + +impl fmt::Display for ConfigKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_config_key().fmt(f) + } +} diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index 6fb2f8503..2059bb7f0 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -33,6 +33,12 @@ use crate::util::{IntoUrl, IntoUrlWithBase}; mod de; use de::Deserializer; +mod value; +pub use value::{Definition, OptValue, Value}; + +mod key; +use key::ConfigKey; + /// Configuration information for cargo. This is not specific to a build, it is information /// relating to cargo itself. /// @@ -380,10 +386,9 @@ impl Config { T: FromStr, ::Err: fmt::Display, { - let key = key.to_env(); - match self.env.get(&key) { + match self.env.get(key.as_env_key()) { Some(value) => { - let definition = Definition::Environment(key); + let definition = Definition::Environment(key.as_env_key().to_string()); Ok(Some(Value { val: value .parse() @@ -396,15 +401,14 @@ impl Config { } fn has_key(&self, key: &ConfigKey) -> bool { - let env_key = key.to_env(); - if self.env.get(&env_key).is_some() { + if self.env.get(key.as_env_key()).is_some() { return true; } - let env_pattern = format!("{}_", env_key); + let env_pattern = format!("{}_", key.as_env_key()); if self.env.keys().any(|k| k.starts_with(&env_pattern)) { return true; } - if let Ok(o_cv) = self.get_cv(&key.to_config()) { + if let Ok(o_cv) = self.get_cv(key.as_config_key()) { if o_cv.is_some() { return true; } @@ -421,14 +425,13 @@ impl Config { match self.get_env(key)? { Some(v) => Ok(Some(v)), None => { - let config_key = key.to_config(); - let o_cv = self.get_cv(&config_key)?; + let o_cv = self.get_cv(key.as_config_key())?; match o_cv { Some(CV::String(s, path)) => Ok(Some(Value { val: s, definition: Definition::Path(path), })), - Some(cv) => Err(ConfigError::expected(&config_key, "a string", &cv)), + Some(cv) => Err(ConfigError::expected(key.as_config_key(), "a string", &cv)), None => Ok(None), } } @@ -444,14 +447,17 @@ impl Config { match self.get_env(key)? { Some(v) => Ok(Some(v)), None => { - let config_key = key.to_config(); - let o_cv = self.get_cv(&config_key)?; + let o_cv = self.get_cv(key.as_config_key())?; match o_cv { Some(CV::Boolean(b, path)) => Ok(Some(Value { val: b, definition: Definition::Path(path), })), - Some(cv) => Err(ConfigError::expected(&config_key, "true/false", &cv)), + Some(cv) => Err(ConfigError::expected( + key.as_config_key(), + "true/false", + &cv, + )), None => Ok(None), } } @@ -548,15 +554,18 @@ impl Config { } fn get_integer(&self, key: &ConfigKey) -> Result, ConfigError> { - let config_key = key.to_config(); match self.get_env::(key)? { Some(v) => Ok(Some(v)), - None => match self.get_cv(&config_key)? { + None => match self.get_cv(key.as_config_key())? { Some(CV::Integer(i, path)) => Ok(Some(Value { val: i, definition: Definition::Path(path), })), - Some(cv) => Err(ConfigError::expected(&config_key, "an integer", &cv)), + Some(cv) => Err(ConfigError::expected( + key.as_config_key(), + "an integer", + &cv, + )), None => Ok(None), }, } @@ -1012,83 +1021,6 @@ impl Config { pub fn release_package_cache_lock(&self) {} } -/// A segment of a config key. -/// -/// Config keys are split on dots for regular keys, or underscores for -/// environment keys. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -enum ConfigKeyPart { - /// Case-insensitive part (checks uppercase in environment keys). - Part(String), - /// Case-sensitive part (environment keys must match exactly). - CasePart(String), -} - -impl ConfigKeyPart { - fn to_env(&self) -> String { - match self { - ConfigKeyPart::Part(s) => s.replace("-", "_").to_uppercase(), - ConfigKeyPart::CasePart(s) => s.clone(), - } - } - - fn to_config(&self) -> String { - match self { - ConfigKeyPart::Part(s) => s.clone(), - ConfigKeyPart::CasePart(s) => s.clone(), - } - } -} - -/// Key for a configuration variable. -#[derive(Debug, Clone)] -pub(crate) struct ConfigKey(Vec); - -impl ConfigKey { - fn from_str(key: &str) -> ConfigKey { - ConfigKey( - key.split('.') - .map(|p| ConfigKeyPart::Part(p.to_string())) - .collect(), - ) - } - - fn join(&self, next: ConfigKeyPart) -> ConfigKey { - let mut res = self.clone(); - res.0.push(next); - res - } - - fn to_env(&self) -> String { - format!( - "CARGO_{}", - self.0 - .iter() - .map(|p| p.to_env()) - .collect::>() - .join("_") - ) - } - - fn to_config(&self) -> String { - self.0 - .iter() - .map(|p| p.to_config()) - .collect::>() - .join(".") - } - - fn last(&self) -> &ConfigKeyPart { - self.0.last().unwrap() - } -} - -impl fmt::Display for ConfigKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.to_config().fmt(f) - } -} - /// Internal error for serde errors. #[derive(Debug)] pub struct ConfigError { @@ -1116,16 +1048,20 @@ impl ConfigError { } } - fn missing(key: &str) -> ConfigError { + fn missing(key: &ConfigKey) -> ConfigError { ConfigError { - error: failure::format_err!("missing config key `{}`", key), + error: failure::format_err!("missing config key `{}`", key.as_config_key()), definition: None, } } - fn with_key_context(self, key: &str, definition: Definition) -> ConfigError { + fn with_key_context(self, key: &ConfigKey, definition: Definition) -> ConfigError { ConfigError { - error: failure::format_err!("could not load config key `{}`: {}", key, self), + error: failure::format_err!( + "could not load config key `{}`: {}", + key.as_config_key(), + self + ), definition: Some(definition), } } @@ -1187,19 +1123,6 @@ pub enum ConfigValue { Boolean(bool, PathBuf), } -pub struct Value { - pub val: T, - pub definition: Definition, -} - -pub type OptValue = Option>; - -#[derive(Clone, Debug)] -pub enum Definition { - Path(PathBuf), - Environment(String), -} - impl fmt::Debug for ConfigValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -1381,24 +1304,6 @@ impl ConfigValue { } } -impl Definition { - pub fn root<'a>(&'a self, config: &'a Config) -> &'a Path { - match *self { - Definition::Path(ref p) => p.parent().unwrap().parent().unwrap(), - Definition::Environment(_) => config.cwd(), - } - } -} - -impl fmt::Display for Definition { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Definition::Path(ref p) => p.display().fmt(f), - Definition::Environment(ref key) => write!(f, "environment variable `{}`", key), - } - } -} - pub fn homedir(cwd: &Path) -> Option { ::home::cargo_home_with_cwd(cwd).ok() } diff --git a/src/cargo/util/config/value.rs b/src/cargo/util/config/value.rs new file mode 100644 index 000000000..dd652e8cc --- /dev/null +++ b/src/cargo/util/config/value.rs @@ -0,0 +1,157 @@ +use crate::util::config::Config; +use serde::de; +use std::fmt; +use std::marker; +use std::path::{Path, PathBuf}; + +pub struct Value { + pub val: T, + pub definition: Definition, +} + +pub type OptValue = Option>; + +pub(crate) const VALUE_FIELD: &str = "$__cargo_private_value"; +pub(crate) const DEFINITION_FIELD: &str = "$__cargo_private_definition"; +pub(crate) const NAME: &str = "$__cargo_private_Value"; +pub(crate) static FIELDS: [&str; 2] = [VALUE_FIELD, DEFINITION_FIELD]; + +#[derive(Clone, Debug)] +pub enum Definition { + Path(PathBuf), + Environment(String), +} + +impl Definition { + pub fn root<'a>(&'a self, config: &'a Config) -> &'a Path { + match self { + Definition::Path(p) => p.parent().unwrap().parent().unwrap(), + Definition::Environment(_) => config.cwd(), + } + } +} + +impl fmt::Display for Definition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Definition::Path(p) => p.display().fmt(f), + Definition::Environment(key) => write!(f, "environment variable `{}`", key), + } + } +} + +impl<'de, T> de::Deserialize<'de> for Value +where + T: de::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + struct ValueVisitor { + _marker: marker::PhantomData, + } + + impl<'de, T> de::Visitor<'de> for ValueVisitor + where + T: de::Deserialize<'de>, + { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a value") + } + + fn visit_map(self, mut visitor: V) -> Result, V::Error> + where + V: de::MapAccess<'de>, + { + let value = visitor.next_key::()?; + if value.is_none() { + return Err(de::Error::custom("value not found")); + } + let val: T = visitor.next_value()?; + + let definition = visitor.next_key::()?; + if definition.is_none() { + return Err(de::Error::custom("definition not found")); + } + let definition: Definition = visitor.next_value()?; + Ok(Value { val, definition }) + } + } + + deserializer.deserialize_struct( + NAME, + &FIELDS, + ValueVisitor { + _marker: marker::PhantomData, + }, + ) + } +} + +struct FieldVisitor { + expected: &'static str, +} + +impl<'de> de::Visitor<'de> for FieldVisitor { + type Value = (); + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a valid value field") + } + + fn visit_str(self, s: &str) -> Result<(), E> + where + E: de::Error, + { + if s == self.expected { + Ok(()) + } else { + Err(de::Error::custom("expected field with custom name")) + } + } +} + +struct ValueKey; + +impl<'de> de::Deserialize<'de> for ValueKey { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_identifier(FieldVisitor { + expected: VALUE_FIELD, + })?; + Ok(ValueKey) + } +} + +struct DefinitionKey; + +impl<'de> de::Deserialize<'de> for DefinitionKey { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_identifier(FieldVisitor { + expected: DEFINITION_FIELD, + })?; + Ok(DefinitionKey) + } +} + +impl<'de> de::Deserialize<'de> for Definition { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let (discr, value) = <(u32, String)>::deserialize(deserializer)?; + if discr == 0 { + Ok(Definition::Path(value.into())) + } else { + Ok(Definition::Environment(value)) + } + } +} diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index c06b76d81..341414278 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -643,7 +643,7 @@ expected a list, but found a integer for `l3` in [..]/.cargo/config", assert_error( config.get::("bad-env").unwrap_err(), "error in environment variable `CARGO_BAD_ENV`: \ - could not parse TOML list: invalid number at line 1 column 10", + could not parse TOML list: invalid number at line 1 column 8", ); // Try some other sequence-like types.