mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
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:
parent
0f73320ee7
commit
c0baf844e3
@ -1,14 +1,16 @@
|
|||||||
//! Support for deserializing configuration via `serde`
|
//! Support for deserializing configuration via `serde`
|
||||||
|
|
||||||
use crate::util::config::{Config, ConfigError, ConfigKey, ConfigKeyPart};
|
use crate::util::config::value;
|
||||||
use crate::util::config::{ConfigValue as CV, Value, Definition};
|
use crate::util::config::{Config, ConfigError, ConfigKey};
|
||||||
use std::path::PathBuf;
|
use crate::util::config::{ConfigValue as CV, Definition, Value};
|
||||||
use serde::{de, de::IntoDeserializer};
|
use serde::{de, de::IntoDeserializer};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
/// Serde deserializer used to convert config values to a target type using
|
/// Serde deserializer used to convert config values to a target type using
|
||||||
/// `Config::get`.
|
/// `Config::get`.
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct Deserializer<'config> {
|
pub(crate) struct Deserializer<'config> {
|
||||||
pub(crate) config: &'config Config,
|
pub(crate) config: &'config Config,
|
||||||
pub(crate) key: ConfigKey,
|
pub(crate) key: ConfigKey,
|
||||||
@ -21,10 +23,10 @@ macro_rules! deserialize_method {
|
|||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
let v = self.config.$getter(&self.key)?.ok_or_else(||
|
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 Value{val, definition} = v;
|
||||||
let res: Result<V::Value, ConfigError> = visitor.$visit(val);
|
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
|
// Future note: If you ever need to deserialize a non-self describing
|
||||||
// map type, this should implement a starts_with check (similar to how
|
// map type, this should implement a starts_with check (similar to how
|
||||||
// ConfigMapAccess does).
|
// 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" {
|
let res: Result<V::Value, ConfigError> = if v == "true" || v == "false" {
|
||||||
visitor.visit_bool(v.parse().unwrap())
|
visitor.visit_bool(v.parse().unwrap())
|
||||||
} else if let Ok(v) = v.parse::<i64>() {
|
} 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.starts_with('[')
|
||||||
&& v.ends_with(']')
|
&& v.ends_with(']')
|
||||||
{
|
{
|
||||||
visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?)
|
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
|
||||||
} else {
|
} else {
|
||||||
visitor.visit_string(v.clone())
|
visitor.visit_string(v.clone())
|
||||||
};
|
};
|
||||||
return res.map_err(|e| {
|
return res.map_err(|e| {
|
||||||
e.with_key_context(
|
e.with_key_context(
|
||||||
&self.key.to_config(),
|
&self.key,
|
||||||
Definition::Environment(self.key.to_env()),
|
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 {
|
if let Some(cv) = o_cv {
|
||||||
let res: (Result<V::Value, ConfigError>, PathBuf) = match cv {
|
let res: (Result<V::Value, ConfigError>, PathBuf) = match cv {
|
||||||
CV::Integer(i, path) => (visitor.visit_i64(i), path),
|
CV::Integer(i, path) => (visitor.visit_i64(i), path),
|
||||||
CV::String(s, path) => (visitor.visit_string(s), path),
|
CV::String(s, path) => (visitor.visit_string(s), path),
|
||||||
CV::List(_, path) => (
|
CV::List(_, path) => (visitor.visit_seq(ConfigSeqAccess::new(self.clone())?), path),
|
||||||
visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?),
|
|
||||||
path,
|
|
||||||
),
|
|
||||||
CV::Table(_, path) => (
|
CV::Table(_, path) => (
|
||||||
visitor.visit_map(ConfigMapAccess::new_map(self.config, self.key.clone())?),
|
visitor.visit_map(ConfigMapAccess::new_map(self.clone())?),
|
||||||
path,
|
path,
|
||||||
),
|
),
|
||||||
CV::Boolean(b, path) => (visitor.visit_bool(b), path),
|
CV::Boolean(b, path) => (visitor.visit_bool(b), path),
|
||||||
};
|
};
|
||||||
let (res, path) = res;
|
let (res, path) = res;
|
||||||
return res
|
return res.map_err(|e| e.with_key_context(&self.key, Definition::Path(path)));
|
||||||
.map_err(|e| e.with_key_context(&self.key.to_config(), Definition::Path(path)));
|
|
||||||
}
|
}
|
||||||
Err(ConfigError::missing(&self.key.to_config()))
|
Err(ConfigError::missing(&self.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize_method!(deserialize_bool, visit_bool, get_bool_priv);
|
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>(
|
fn deserialize_struct<V>(
|
||||||
self,
|
self,
|
||||||
_name: &'static str,
|
name: &'static str,
|
||||||
fields: &'static [&'static str],
|
fields: &'static [&'static str],
|
||||||
visitor: V,
|
visitor: V,
|
||||||
) -> Result<V::Value, Self::Error>
|
) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
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>
|
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
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>
|
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
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>
|
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?)
|
visitor.visit_seq(ConfigSeqAccess::new(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_tuple_struct<V>(
|
fn deserialize_tuple_struct<V>(
|
||||||
@ -147,7 +151,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
|
|||||||
where
|
where
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
visitor.visit_seq(ConfigSeqAccess::new(self.config, &self.key)?)
|
visitor.visit_seq(ConfigSeqAccess::new(self)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_newtype_struct<V>(
|
fn deserialize_newtype_struct<V>(
|
||||||
@ -169,7 +173,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
|
|||||||
.to_string();
|
.to_string();
|
||||||
visitor.visit_newtype_struct(path.into_deserializer())
|
visitor.visit_newtype_struct(path.into_deserializer())
|
||||||
}
|
}
|
||||||
None => Err(ConfigError::missing(&self.key.to_config())),
|
None => Err(ConfigError::missing(&self.key)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
visitor.visit_newtype_struct(self)
|
visitor.visit_newtype_struct(self)
|
||||||
@ -185,70 +189,76 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ConfigMapAccess<'config> {
|
struct ConfigMapAccess<'config> {
|
||||||
config: &'config Config,
|
de: Deserializer<'config>,
|
||||||
key: ConfigKey,
|
set_iter: <HashSet<KeyKind> as IntoIterator>::IntoIter,
|
||||||
set_iter: <HashSet<ConfigKeyPart> as IntoIterator>::IntoIter,
|
next: Option<KeyKind>,
|
||||||
next: Option<ConfigKeyPart>,
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
enum KeyKind {
|
||||||
|
Normal(String),
|
||||||
|
CaseSensitive(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'config> ConfigMapAccess<'config> {
|
impl<'config> ConfigMapAccess<'config> {
|
||||||
fn new_map(
|
fn new_map(de: Deserializer<'config>) -> Result<ConfigMapAccess<'config>, ConfigError> {
|
||||||
config: &'config Config,
|
|
||||||
key: ConfigKey,
|
|
||||||
) -> Result<ConfigMapAccess<'config>, ConfigError> {
|
|
||||||
let mut set = HashSet::new();
|
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>>`
|
// `v: Value<HashMap<String, CV>>`
|
||||||
for (key, _value) in v.val.drain() {
|
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_`
|
// `CARGO_PROFILE_DEV_OVERRIDES_`
|
||||||
let env_pattern = format!("{}_", key.to_env());
|
let env_pattern = format!("{}_", de.key.as_env_key());
|
||||||
for env_key in config.env.keys() {
|
for env_key in de.config.env.keys() {
|
||||||
if env_key.starts_with(&env_pattern) {
|
if env_key.starts_with(&env_pattern) {
|
||||||
// `CARGO_PROFILE_DEV_OVERRIDES_bar_OPT_LEVEL = 3`
|
// `CARGO_PROFILE_DEV_OVERRIDES_bar_OPT_LEVEL = 3`
|
||||||
let rest = &env_key[env_pattern.len()..];
|
let rest = &env_key[env_pattern.len()..];
|
||||||
// `rest = bar_OPT_LEVEL`
|
// `rest = bar_OPT_LEVEL`
|
||||||
let part = rest.splitn(2, '_').next().unwrap();
|
let part = rest.splitn(2, '_').next().unwrap();
|
||||||
// `part = "bar"`
|
// `part = "bar"`
|
||||||
set.insert(ConfigKeyPart::CasePart(part.to_string()));
|
set.insert(KeyKind::CaseSensitive(part.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ConfigMapAccess {
|
Ok(ConfigMapAccess {
|
||||||
config,
|
de,
|
||||||
key,
|
|
||||||
set_iter: set.into_iter(),
|
set_iter: set.into_iter(),
|
||||||
next: None,
|
next: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_struct(
|
fn new_struct(
|
||||||
config: &'config Config,
|
de: Deserializer<'config>,
|
||||||
key: ConfigKey,
|
|
||||||
fields: &'static [&'static str],
|
fields: &'static [&'static str],
|
||||||
) -> Result<ConfigMapAccess<'config>, ConfigError> {
|
) -> Result<ConfigMapAccess<'config>, ConfigError> {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for field in fields {
|
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() {
|
for (t_key, value) in v.val.drain() {
|
||||||
let part = ConfigKeyPart::Part(t_key);
|
if set.contains(&KeyKind::Normal(t_key.to_string())) {
|
||||||
if !set.contains(&part) {
|
continue;
|
||||||
config.shell().warn(format!(
|
|
||||||
"unused key `{}` in config file `{}`",
|
|
||||||
key.join(part).to_config(),
|
|
||||||
value.definition_path().display()
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
|
de.config.shell().warn(format!(
|
||||||
|
"unused key `{}.{}` in config file `{}`",
|
||||||
|
de.key.as_config_key(),
|
||||||
|
t_key,
|
||||||
|
value.definition_path().display()
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ConfigMapAccess {
|
Ok(ConfigMapAccess {
|
||||||
config,
|
de,
|
||||||
key,
|
|
||||||
set_iter: set.into_iter(),
|
set_iter: set.into_iter(),
|
||||||
next: None,
|
next: None,
|
||||||
})
|
})
|
||||||
@ -264,9 +274,12 @@ impl<'de, 'config> de::MapAccess<'de> for ConfigMapAccess<'config> {
|
|||||||
{
|
{
|
||||||
match self.set_iter.next() {
|
match self.set_iter.next() {
|
||||||
Some(key) => {
|
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);
|
self.next = Some(key);
|
||||||
seed.deserialize(de_key.into_deserializer()).map(Some)
|
return result;
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
@ -276,12 +289,16 @@ impl<'de, 'config> de::MapAccess<'de> for ConfigMapAccess<'config> {
|
|||||||
where
|
where
|
||||||
V: de::DeserializeSeed<'de>,
|
V: de::DeserializeSeed<'de>,
|
||||||
{
|
{
|
||||||
let next_key = self.next.take().expect("next field missing");
|
match self.next.take().expect("next field missing") {
|
||||||
let next_key = self.key.join(next_key);
|
KeyKind::Normal(key) => self.de.key.push(&key),
|
||||||
seed.deserialize(Deserializer {
|
KeyKind::CaseSensitive(key) => self.de.key.push_sensitive(&key),
|
||||||
config: self.config,
|
}
|
||||||
key: next_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 {
|
impl ConfigSeqAccess {
|
||||||
fn new(config: &Config, key: &ConfigKey) -> Result<ConfigSeqAccess, ConfigError> {
|
fn new(de: Deserializer<'_>) -> Result<ConfigSeqAccess, ConfigError> {
|
||||||
let mut res = Vec::new();
|
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 {
|
for (s, path) in v.val {
|
||||||
res.push((s, Definition::Path(path)));
|
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.
|
// Parse an environment string as a TOML array.
|
||||||
let env_key = key.to_env();
|
if let Some(v) = de.config.env.get(de.key.as_env_key()) {
|
||||||
let def = Definition::Environment(env_key.clone());
|
let def = Definition::Environment(de.key.as_env_key().to_string());
|
||||||
if let Some(v) = config.env.get(&env_key) {
|
|
||||||
if !(v.starts_with('[') && v.ends_with(']')) {
|
if !(v.starts_with('[') && v.ends_with(']')) {
|
||||||
return Err(ConfigError::new(
|
return Err(ConfigError::new(
|
||||||
format!("should have TOML list syntax, found `{}`", v),
|
format!("should have TOML list syntax, found `{}`", v),
|
||||||
def,
|
def,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let temp_key = key.last().to_env();
|
let toml_s = format!("value={}", v);
|
||||||
let toml_s = format!("{}={}", temp_key, v);
|
|
||||||
let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| {
|
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())
|
ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
|
||||||
})?;
|
})?;
|
||||||
let values = toml_v
|
let values = toml_v
|
||||||
.as_table()
|
.as_table()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get(&temp_key)
|
.get("value")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_array()
|
.as_array()
|
||||||
.expect("env var was not 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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
68
src/cargo/util/config/key.rs
Normal file
68
src/cargo/util/config/key.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,12 @@ use crate::util::{IntoUrl, IntoUrlWithBase};
|
|||||||
mod de;
|
mod de;
|
||||||
use de::Deserializer;
|
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
|
/// Configuration information for cargo. This is not specific to a build, it is information
|
||||||
/// relating to cargo itself.
|
/// relating to cargo itself.
|
||||||
///
|
///
|
||||||
@ -380,10 +386,9 @@ impl Config {
|
|||||||
T: FromStr,
|
T: FromStr,
|
||||||
<T as FromStr>::Err: fmt::Display,
|
<T as FromStr>::Err: fmt::Display,
|
||||||
{
|
{
|
||||||
let key = key.to_env();
|
match self.env.get(key.as_env_key()) {
|
||||||
match self.env.get(&key) {
|
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let definition = Definition::Environment(key);
|
let definition = Definition::Environment(key.as_env_key().to_string());
|
||||||
Ok(Some(Value {
|
Ok(Some(Value {
|
||||||
val: value
|
val: value
|
||||||
.parse()
|
.parse()
|
||||||
@ -396,15 +401,14 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn has_key(&self, key: &ConfigKey) -> bool {
|
fn has_key(&self, key: &ConfigKey) -> bool {
|
||||||
let env_key = key.to_env();
|
if self.env.get(key.as_env_key()).is_some() {
|
||||||
if self.env.get(&env_key).is_some() {
|
|
||||||
return true;
|
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)) {
|
if self.env.keys().any(|k| k.starts_with(&env_pattern)) {
|
||||||
return true;
|
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() {
|
if o_cv.is_some() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -421,14 +425,13 @@ impl Config {
|
|||||||
match self.get_env(key)? {
|
match self.get_env(key)? {
|
||||||
Some(v) => Ok(Some(v)),
|
Some(v) => Ok(Some(v)),
|
||||||
None => {
|
None => {
|
||||||
let config_key = key.to_config();
|
let o_cv = self.get_cv(key.as_config_key())?;
|
||||||
let o_cv = self.get_cv(&config_key)?;
|
|
||||||
match o_cv {
|
match o_cv {
|
||||||
Some(CV::String(s, path)) => Ok(Some(Value {
|
Some(CV::String(s, path)) => Ok(Some(Value {
|
||||||
val: s,
|
val: s,
|
||||||
definition: Definition::Path(path),
|
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),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -444,14 +447,17 @@ impl Config {
|
|||||||
match self.get_env(key)? {
|
match self.get_env(key)? {
|
||||||
Some(v) => Ok(Some(v)),
|
Some(v) => Ok(Some(v)),
|
||||||
None => {
|
None => {
|
||||||
let config_key = key.to_config();
|
let o_cv = self.get_cv(key.as_config_key())?;
|
||||||
let o_cv = self.get_cv(&config_key)?;
|
|
||||||
match o_cv {
|
match o_cv {
|
||||||
Some(CV::Boolean(b, path)) => Ok(Some(Value {
|
Some(CV::Boolean(b, path)) => Ok(Some(Value {
|
||||||
val: b,
|
val: b,
|
||||||
definition: Definition::Path(path),
|
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),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -548,15 +554,18 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_integer(&self, key: &ConfigKey) -> Result<OptValue<i64>, ConfigError> {
|
fn get_integer(&self, key: &ConfigKey) -> Result<OptValue<i64>, ConfigError> {
|
||||||
let config_key = key.to_config();
|
|
||||||
match self.get_env::<i64>(key)? {
|
match self.get_env::<i64>(key)? {
|
||||||
Some(v) => Ok(Some(v)),
|
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 {
|
Some(CV::Integer(i, path)) => Ok(Some(Value {
|
||||||
val: i,
|
val: i,
|
||||||
definition: Definition::Path(path),
|
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),
|
None => Ok(None),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1012,83 +1021,6 @@ impl Config {
|
|||||||
pub fn release_package_cache_lock(&self) {}
|
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.
|
/// Internal error for serde errors.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConfigError {
|
pub struct ConfigError {
|
||||||
@ -1116,16 +1048,20 @@ impl ConfigError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn missing(key: &str) -> ConfigError {
|
fn missing(key: &ConfigKey) -> ConfigError {
|
||||||
ConfigError {
|
ConfigError {
|
||||||
error: failure::format_err!("missing config key `{}`", key),
|
error: failure::format_err!("missing config key `{}`", key.as_config_key()),
|
||||||
definition: None,
|
definition: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_key_context(self, key: &str, definition: Definition) -> ConfigError {
|
fn with_key_context(self, key: &ConfigKey, definition: Definition) -> ConfigError {
|
||||||
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),
|
definition: Some(definition),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1187,19 +1123,6 @@ pub enum ConfigValue {
|
|||||||
Boolean(bool, PathBuf),
|
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 {
|
impl fmt::Debug for ConfigValue {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
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> {
|
pub fn homedir(cwd: &Path) -> Option<PathBuf> {
|
||||||
::home::cargo_home_with_cwd(cwd).ok()
|
::home::cargo_home_with_cwd(cwd).ok()
|
||||||
}
|
}
|
||||||
|
157
src/cargo/util/config/value.rs
Normal file
157
src/cargo/util/config/value.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -643,7 +643,7 @@ expected a list, but found a integer for `l3` in [..]/.cargo/config",
|
|||||||
assert_error(
|
assert_error(
|
||||||
config.get::<L>("bad-env").unwrap_err(),
|
config.get::<L>("bad-env").unwrap_err(),
|
||||||
"error in environment variable `CARGO_BAD_ENV`: \
|
"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.
|
// Try some other sequence-like types.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user