Skip to content

Commit 37ace88

Browse files
committed
Finish implementing Value, use it in helpers
Rewrite helpers like `get_bool` to use `get::<Option<Value<bool>>>` instead of duplicating the logic that's already with the typed access of configuration. This is more along the effort to centralize all deserialization of configuration into typed values instead of using ad-hoc accessors in a number of locations.
1 parent c0baf84 commit 37ace88

File tree

5 files changed

+132
-94
lines changed

5 files changed

+132
-94
lines changed

src/cargo/util/config/de.rs

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,7 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
113113
V: de::Visitor<'de>,
114114
{
115115
if name == value::NAME && fields == value::FIELDS {
116-
return visitor.visit_map(ValueDeserializer {
117-
hits: 0,
118-
deserializer: self,
119-
});
116+
return visitor.visit_map(ValueDeserializer { hits: 0, de: self });
120117
}
121118
visitor.visit_map(ConfigMapAccess::new_struct(self, fields)?)
122119
}
@@ -154,37 +151,11 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
154151
visitor.visit_seq(ConfigSeqAccess::new(self)?)
155152
}
156153

157-
fn deserialize_newtype_struct<V>(
158-
self,
159-
name: &'static str,
160-
visitor: V,
161-
) -> Result<V::Value, Self::Error>
162-
where
163-
V: de::Visitor<'de>,
164-
{
165-
if name == "ConfigRelativePath" {
166-
match self.config.get_string_priv(&self.key)? {
167-
Some(v) => {
168-
let path = v
169-
.definition
170-
.root(self.config)
171-
.join(v.val)
172-
.display()
173-
.to_string();
174-
visitor.visit_newtype_struct(path.into_deserializer())
175-
}
176-
None => Err(ConfigError::missing(&self.key)),
177-
}
178-
} else {
179-
visitor.visit_newtype_struct(self)
180-
}
181-
}
182-
183154
// These aren't really supported, yet.
184155
serde::forward_to_deserialize_any! {
185156
f32 f64 char str bytes
186157
byte_buf unit unit_struct
187-
enum identifier ignored_any
158+
enum identifier ignored_any newtype_struct
188159
}
189160
}
190161

@@ -371,7 +342,7 @@ impl<'de> de::SeqAccess<'de> for ConfigSeqAccess {
371342

372343
struct ValueDeserializer<'config> {
373344
hits: u32,
374-
deserializer: Deserializer<'config>,
345+
de: Deserializer<'config>,
375346
}
376347

377348
impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> {
@@ -397,18 +368,80 @@ impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> {
397368
where
398369
V: de::DeserializeSeed<'de>,
399370
{
371+
macro_rules! bail {
372+
($($t:tt)*) => (return Err(failure::format_err!($($t)*).into()))
373+
}
374+
375+
// If this is the first time around we deserialize the `value` field
376+
// which is the actual deserializer
400377
if self.hits == 1 {
401-
seed.deserialize(Deserializer {
402-
config: self.deserializer.config,
403-
key: self.deserializer.key.clone(),
404-
})
405-
} else {
406-
// let env = self.deserializer.key.to_env();
407-
// if self.deserializer.config.env.contains_key(&env) {
408-
// } else {
409-
// }
410-
// if let someself.deserializer.config.get_env(&self.deserializer.key)
411-
panic!()
378+
return seed.deserialize(self.de.clone());
379+
}
380+
381+
// ... otherwise we're deserializing the `definition` field, so we need
382+
// to figure out where the field we just deserialized was defined at.
383+
let env = self.de.key.as_env_key();
384+
if self.de.config.env.contains_key(env) {
385+
return seed.deserialize(Tuple2Deserializer(1i32, env));
386+
}
387+
let val = match self.de.config.get_cv(self.de.key.as_config_key())? {
388+
Some(val) => val,
389+
None => bail!("failed to find definition of `{}`", self.de.key),
390+
};
391+
let path = match val.definition_path().to_str() {
392+
Some(s) => s,
393+
None => bail!("failed to convert {:?} to utf-8", val.definition_path()),
394+
};
395+
seed.deserialize(Tuple2Deserializer(0i32, path))
396+
}
397+
}
398+
399+
struct Tuple2Deserializer<T, U>(T, U);
400+
401+
impl<'de, T, U> de::Deserializer<'de> for Tuple2Deserializer<T, U>
402+
where
403+
T: IntoDeserializer<'de, ConfigError>,
404+
U: IntoDeserializer<'de, ConfigError>,
405+
{
406+
type Error = ConfigError;
407+
408+
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, ConfigError>
409+
where
410+
V: de::Visitor<'de>,
411+
{
412+
struct SeqVisitor<T, U> {
413+
first: Option<T>,
414+
second: Option<U>,
412415
}
416+
impl<'de, T, U> de::SeqAccess<'de> for SeqVisitor<T, U>
417+
where
418+
T: IntoDeserializer<'de, ConfigError>,
419+
U: IntoDeserializer<'de, ConfigError>,
420+
{
421+
type Error = ConfigError;
422+
fn next_element_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
423+
where
424+
K: de::DeserializeSeed<'de>,
425+
{
426+
if let Some(first) = self.first.take() {
427+
return seed.deserialize(first.into_deserializer()).map(Some);
428+
}
429+
if let Some(second) = self.second.take() {
430+
return seed.deserialize(second.into_deserializer()).map(Some);
431+
}
432+
Ok(None)
433+
}
434+
}
435+
436+
visitor.visit_seq(SeqVisitor {
437+
first: Some(self.0),
438+
second: Some(self.1),
439+
})
440+
}
441+
442+
serde::forward_to_deserialize_any! {
443+
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
444+
bytes byte_buf map struct option unit newtype_struct
445+
ignored_any unit_struct tuple_struct tuple enum identifier
413446
}
414447
}

src/cargo/util/config/mod.rs

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ pub use value::{Definition, OptValue, Value};
3939
mod key;
4040
use key::ConfigKey;
4141

42+
mod path;
43+
pub use path::ConfigRelativePath;
44+
4245
/// Configuration information for cargo. This is not specific to a build, it is information
4346
/// relating to cargo itself.
4447
///
@@ -417,8 +420,7 @@ impl Config {
417420
}
418421

419422
pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
420-
self.get_string_priv(&ConfigKey::from_str(key))
421-
.map_err(|e| e.into())
423+
self.get::<Option<Value<String>>>(key)
422424
}
423425

424426
fn get_string_priv(&self, key: &ConfigKey) -> Result<OptValue<String>, ConfigError> {
@@ -439,8 +441,7 @@ impl Config {
439441
}
440442

441443
pub fn get_bool(&self, key: &str) -> CargoResult<OptValue<bool>> {
442-
self.get_bool_priv(&ConfigKey::from_str(key))
443-
.map_err(|e| e.into())
444+
self.get::<Option<Value<bool>>>(key)
444445
}
445446

446447
fn get_bool_priv(&self, key: &ConfigKey) -> Result<OptValue<bool>, ConfigError> {
@@ -464,6 +465,15 @@ impl Config {
464465
}
465466
}
466467

468+
pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
469+
self.get::<Option<Value<ConfigRelativePath>>>(key).map(|v| {
470+
v.map(|v| Value {
471+
val: v.val.resolve(self),
472+
definition: v.definition,
473+
})
474+
})
475+
}
476+
467477
fn string_to_path(&self, value: String, definition: &Definition) -> PathBuf {
468478
let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
469479
if is_path {
@@ -474,17 +484,6 @@ impl Config {
474484
}
475485
}
476486

477-
pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
478-
if let Some(val) = self.get_string(key)? {
479-
Ok(Some(Value {
480-
val: self.string_to_path(val.val, &val.definition),
481-
definition: val.definition,
482-
}))
483-
} else {
484-
Ok(None)
485-
}
486-
}
487-
488487
pub fn get_path_and_args(&self, key: &str) -> CargoResult<OptValue<(PathBuf, Vec<String>)>> {
489488
if let Some(mut val) = self.get_list_or_split_string(key)? {
490489
if !val.val.is_empty() {
@@ -514,24 +513,26 @@ impl Config {
514513
}
515514

516515
pub fn get_list_or_split_string(&self, key: &str) -> CargoResult<OptValue<Vec<String>>> {
517-
if let Some(value) = self.get_env::<String>(&ConfigKey::from_str(key))? {
518-
return Ok(Some(Value {
519-
val: value.val.split(' ').map(str::to_string).collect(),
520-
definition: value.definition,
521-
}));
516+
#[derive(Deserialize)]
517+
#[serde(untagged)]
518+
enum Target {
519+
String(String),
520+
List(Vec<String>),
522521
}
523522

524-
match self.get_cv(key)? {
525-
Some(CV::List(i, path)) => Ok(Some(Value {
526-
val: i.into_iter().map(|(s, _)| s).collect(),
527-
definition: Definition::Path(path),
528-
})),
529-
Some(CV::String(i, path)) => Ok(Some(Value {
530-
val: i.split(' ').map(str::to_string).collect(),
531-
definition: Definition::Path(path),
532-
})),
533-
Some(val) => self.expected("list or string", key, &val),
523+
match self.get::<Option<Value<Target>>>(key)? {
534524
None => Ok(None),
525+
Some(Value {
526+
val: Target::String(s),
527+
definition,
528+
}) => Ok(Some(Value {
529+
val: s.split(' ').map(str::to_string).collect(),
530+
definition,
531+
})),
532+
Some(Value {
533+
val: Target::List(val),
534+
definition,
535+
}) => Ok(Some(Value { val, definition })),
535536
}
536537
}
537538

@@ -549,8 +550,7 @@ impl Config {
549550
// Recommended to use `get` if you want a specific type, such as an unsigned value.
550551
// Example: `config.get::<Option<u32>>("some.key")?`.
551552
pub fn get_i64(&self, key: &str) -> CargoResult<OptValue<i64>> {
552-
self.get_integer(&ConfigKey::from_str(key))
553-
.map_err(|e| e.into())
553+
self.get::<Option<Value<i64>>>(key)
554554
}
555555

556556
fn get_integer(&self, key: &ConfigKey) -> Result<OptValue<i64>, ConfigError> {
@@ -1102,18 +1102,6 @@ impl From<failure::Error> for ConfigError {
11021102
}
11031103
}
11041104

1105-
/// Use with the `get` API to fetch a string that will be converted to a
1106-
/// `PathBuf`. Relative paths are converted to absolute paths based on the
1107-
/// location of the config file.
1108-
#[derive(Debug, Eq, PartialEq, Clone, Deserialize)]
1109-
pub struct ConfigRelativePath(PathBuf);
1110-
1111-
impl ConfigRelativePath {
1112-
pub fn path(self) -> PathBuf {
1113-
self.0
1114-
}
1115-
}
1116-
11171105
#[derive(Eq, PartialEq, Clone)]
11181106
pub enum ConfigValue {
11191107
Integer(i64, PathBuf),

src/cargo/util/config/path.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use crate::util::config::{Config, Value};
2+
use serde::Deserialize;
3+
use std::path::PathBuf;
4+
5+
/// Use with the `get` API to fetch a string that will be converted to a
6+
/// `PathBuf`. Relative paths are converted to absolute paths based on the
7+
/// location of the config file.
8+
#[derive(Deserialize)]
9+
#[serde(transparent)]
10+
pub struct ConfigRelativePath(Value<String>);
11+
12+
impl ConfigRelativePath {
13+
pub fn resolve(self, config: &Config) -> PathBuf {
14+
config.string_to_path(self.0.val, &self.0.definition)
15+
}
16+
}

tests/testsuite/bad_config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ fn bad1() {
1717
.with_status(101)
1818
.with_stderr(
1919
"\
20-
[ERROR] expected table for configuration key `target.nonexistent-target`, \
21-
but found string in [..]config
20+
[ERROR] invalid configuration for key `target.nonexistent-target`
21+
expected a table, but found a string for `[..]` in [..]config
2222
",
2323
)
2424
.run();

tests/testsuite/config.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ ns2 = 456
706706
let config = new_config(&[("CARGO_NSE", "987"), ("CARGO_NS2", "654")]);
707707

708708
#[derive(Debug, Deserialize, Eq, PartialEq)]
709+
#[serde(transparent)]
709710
struct NewS(i32);
710711
assert_eq!(config.get::<NewS>("ns").unwrap(), NewS(123));
711712
assert_eq!(config.get::<NewS>("ns2").unwrap(), NewS(654));
@@ -734,35 +735,35 @@ abs = '{}'
734735
config
735736
.get::<config::ConfigRelativePath>("p1")
736737
.unwrap()
737-
.path(),
738+
.resolve(&config),
738739
paths::root().join("foo/bar")
739740
);
740741
assert_eq!(
741742
config
742743
.get::<config::ConfigRelativePath>("p2")
743744
.unwrap()
744-
.path(),
745+
.resolve(&config),
745746
paths::root().join("../abc")
746747
);
747748
assert_eq!(
748749
config
749750
.get::<config::ConfigRelativePath>("p3")
750751
.unwrap()
751-
.path(),
752+
.resolve(&config),
752753
paths::root().join("d/e")
753754
);
754755
assert_eq!(
755756
config
756757
.get::<config::ConfigRelativePath>("abs")
757758
.unwrap()
758-
.path(),
759+
.resolve(&config),
759760
paths::home()
760761
);
761762
assert_eq!(
762763
config
763764
.get::<config::ConfigRelativePath>("epath")
764765
.unwrap()
765-
.path(),
766+
.resolve(&config),
766767
paths::root().join("a/b")
767768
);
768769
}

0 commit comments

Comments
 (0)