diff --git a/src/de.rs b/src/de.rs index e465d607..9267338c 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,10 +1,8 @@ use config::Config; use error::*; use serde::de; -use std::borrow::Cow; -use std::collections::hash_map::Drain; -use std::collections::HashMap; -use std::iter::Peekable; +use std::collections::{HashMap, VecDeque}; +use std::iter::Enumerate; use value::{Value, ValueKind, ValueWithKey, Table}; // TODO: Use a macro or some other magic to reduce the code duplication here @@ -287,13 +285,13 @@ impl<'de, 'a> de::Deserializer<'de> for StrDeserializer<'a> { } struct SeqAccess { - elements: ::std::vec::IntoIter, + elements: Enumerate<::std::vec::IntoIter>, } impl SeqAccess { fn new(elements: Vec) -> Self { SeqAccess { - elements: elements.into_iter(), + elements: elements.into_iter().enumerate(), } } } @@ -306,7 +304,11 @@ impl<'de> de::SeqAccess<'de> for SeqAccess { T: de::DeserializeSeed<'de>, { match self.elements.next() { - Some(value) => seed.deserialize(value).map(Some), + Some((idx, value)) => { + seed.deserialize(value) + .map(Some) + .map_err(|e| e.prepend_index(idx)) + } None => Ok(None), } } @@ -320,15 +322,13 @@ impl<'de> de::SeqAccess<'de> for SeqAccess { } struct MapAccess { - elements: Vec<(String, Value)>, - index: usize, + elements: VecDeque<(String, Value)>, } impl MapAccess { - fn new(mut table: HashMap) -> Self { + fn new(table: HashMap) -> Self { MapAccess { - elements: table.drain().collect(), - index: 0, + elements: table.into_iter().collect(), } } } @@ -340,22 +340,23 @@ impl<'de> de::MapAccess<'de> for MapAccess { where K: de::DeserializeSeed<'de>, { - if self.index >= self.elements.len() { - return Ok(None); - } - - let key_s = &(self.elements[0].0); - let key_de = StrDeserializer(key_s); - let key = de::DeserializeSeed::deserialize(seed, key_de)?; + if let Some(&(ref key_s, _)) = self.elements.front() { + let key_de = StrDeserializer(key_s); + let key = de::DeserializeSeed::deserialize(seed, key_de)?; - Ok(Some(key)) + Ok(Some(key)) + } else { + Ok(None) + } } fn next_value_seed(&mut self, seed: V) -> Result where V: de::DeserializeSeed<'de>, { - de::DeserializeSeed::deserialize(seed, self.elements.remove(0).1) + let (key, value) = self.elements.pop_front().unwrap(); + de::DeserializeSeed::deserialize(seed, value) + .map_err(|e| e.prepend_key(key)) } } diff --git a/src/error.rs b/src/error.rs index 1d348b6d..e3057506 100644 --- a/src/error.rs +++ b/src/error.rs @@ -114,6 +114,43 @@ impl ConfigError { _ => self, } } + + fn prepend(self, segment: String, add_dot: bool) -> Self { + let concat = |key: Option| { + let key = key.unwrap_or_else(String::new); + let dot = if add_dot && key.as_bytes().get(0).unwrap_or(&b'[') != &b'[' { + "." + } else { + "" + }; + format!("{}{}{}", segment, dot, key) + }; + match self { + ConfigError::Type { + origin, + unexpected, + expected, + key, + } => { + ConfigError::Type { + origin, + unexpected, + expected, + key: Some(concat(key)), + } + } + ConfigError::NotFound(key) => ConfigError::NotFound(concat(Some(key))), + _ => self, + } + } + + pub(crate) fn prepend_key(self, key: String) -> Self { + self.prepend(key, true) + } + + pub(crate) fn prepend_index(self, idx: usize) -> Self { + self.prepend(format!("[{}]", idx), false) + } } /// Alias for a `Result` with the error type set to `ConfigError`. diff --git a/tests/errors.rs b/tests/errors.rs index fa2816e5..030ae745 100644 --- a/tests/errors.rs +++ b/tests/errors.rs @@ -92,3 +92,28 @@ fn test_error_enum_de() { ); } +#[test] +fn error_with_path() { + #[derive(Debug, Deserialize)] + struct Inner { + test: i32, + } + + #[derive(Debug, Deserialize)] + struct Outer { + inner: Inner, + } + const CFG: &str = r#" +inner: + test: ABC +"#; + + let mut cfg = Config::new(); + cfg.merge(File::from_str(CFG, FileFormat::Yaml)).unwrap(); + let e = cfg.try_into::().unwrap_err(); + if let ConfigError::Type { key: Some(path), .. } = e { + assert_eq!(path, "inner.test"); + } else { + panic!("Wrong error {:?}", e); + } +}