mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +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`
|
||||
|
||||
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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
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()
|
||||
}
|
||||
|
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(
|
||||
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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user