Refactor ConfigKey to its own file

Also make it a little less allocation-heavy by tweaking the API to
encourage incremental building of the key and incremental destruction as
we walk throughout the configuration tree.
This commit is contained in:
Alex Crichton 2019-09-27 11:34:29 -07:00
parent 0f73320ee7
commit c0baf844e3
5 changed files with 389 additions and 200 deletions

View File

@ -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<V::Value, ConfigError> = 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<V::Value, ConfigError> = if v == "true" || v == "false" {
visitor.visit_bool(v.parse().unwrap())
} else if let Ok(v) = v.parse::<i64>() {
@ -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<V::Value, ConfigError>, 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<V>(
self,
_name: &'static str,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?)
visitor.visit_seq(ConfigSeqAccess::new(self)?)
}
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?)
visitor.visit_seq(ConfigSeqAccess::new(self)?)
}
fn deserialize_tuple_struct<V>(
@ -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<V>(
@ -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: <HashSet<ConfigKeyPart> as IntoIterator>::IntoIter,
next: Option<ConfigKeyPart>,
de: Deserializer<'config>,
set_iter: <HashSet<KeyKind> as IntoIterator>::IntoIter,
next: Option<KeyKind>,
}
#[derive(PartialEq, Eq, Hash)]
enum KeyKind {
Normal(String),
CaseSensitive(String),
}
impl<'config> ConfigMapAccess<'config> {
fn new_map(
config: &'config Config,
key: ConfigKey,
) -> Result<ConfigMapAccess<'config>, ConfigError> {
fn new_map(de: Deserializer<'config>) -> Result<ConfigMapAccess<'config>, 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<HashMap<String, CV>>`
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<ConfigMapAccess<'config>, 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<ConfigSeqAccess, ConfigError> {
fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess, ConfigError> {
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<K>(&mut self, seed: K) -> Result<Option<K::Value>, 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<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
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!()
}
}
}

View File

@ -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)
}
}

View File

@ -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,
<T as 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<OptValue<i64>, ConfigError> {
let config_key = key.to_config();
match self.get_env::<i64>(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<ConfigKeyPart>);
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::<Vec<_>>()
.join("_")
)
}
fn to_config(&self) -> String {
self.0
.iter()
.map(|p| p.to_config())
.collect::<Vec<_>>()
.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<T> {
pub val: T,
pub definition: Definition,
}
pub type OptValue<T> = Option<Value<T>>;
#[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<PathBuf> {
::home::cargo_home_with_cwd(cwd).ok()
}

View File

@ -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<T> {
pub val: T,
pub definition: Definition,
}
pub type OptValue<T> = Option<Value<T>>;
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<T>
where
T: de::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Value<T>, D::Error>
where
D: de::Deserializer<'de>,
{
struct ValueVisitor<T> {
_marker: marker::PhantomData<T>,
}
impl<'de, T> de::Visitor<'de> for ValueVisitor<T>
where
T: de::Deserialize<'de>,
{
type Value = Value<T>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a value")
}
fn visit_map<V>(self, mut visitor: V) -> Result<Value<T>, V::Error>
where
V: de::MapAccess<'de>,
{
let value = visitor.next_key::<ValueKey>()?;
if value.is_none() {
return Err(de::Error::custom("value not found"));
}
let val: T = visitor.next_value()?;
let definition = visitor.next_key::<DefinitionKey>()?;
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<E>(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<D>(deserializer: D) -> Result<ValueKey, D::Error>
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<D>(deserializer: D) -> Result<DefinitionKey, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_identifier(FieldVisitor {
expected: DEFINITION_FIELD,
})?;
Ok(DefinitionKey)
}
}
impl<'de> de::Deserialize<'de> for Definition {
fn deserialize<D>(deserializer: D) -> Result<Definition, D::Error>
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))
}
}
}

View File

@ -643,7 +643,7 @@ expected a list, but found a integer for `l3` in [..]/.cargo/config",
assert_error(
config.get::<L>("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.