From 5eef2539557b9d4eec72c7053457bf3025a3989e Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Sat, 3 Jun 2017 01:22:24 -0700 Subject: More context for errors (value key if possible) --- src/de.rs | 221 ++++++++++++++++++++++++++++++++++++++++++++++++----------- src/error.rs | 33 +++++++-- 2 files changed, 209 insertions(+), 45 deletions(-) diff --git a/src/de.rs b/src/de.rs index 9a9ef58..6487f8b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,107 +1,216 @@ use serde::de; -use value::{Value, ValueKind}; +use value::{Value, ValueWithKey, ValueKind}; use error::*; use std::borrow::Cow; use std::iter::Peekable; use std::collections::HashMap; use std::collections::hash_map::Drain; -impl de::Deserializer for Value { +// TODO: Use a macro or some other magic to reduce the code duplication here + +impl<'de> de::Deserializer<'de> for ValueWithKey<'de> { type Error = ConfigError; #[inline] - fn deserialize(self, visitor: V) -> Result - where V: de::Visitor + fn deserialize_any(self, visitor: V) -> Result + where V: de::Visitor<'de> + { + // Deserialize based on the underlying type + match self.0.kind { + ValueKind::Nil => visitor.visit_unit(), + ValueKind::Integer(i) => visitor.visit_i64(i), + ValueKind::Boolean(b) => visitor.visit_bool(b), + ValueKind::Float(f) => visitor.visit_f64(f), + ValueKind::String(s) => visitor.visit_string(s), + ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), + ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), + } + } + + #[inline] + fn deserialize_bool>(self, visitor: V) -> Result { + visitor.visit_bool(self.into_bool()?) + } + + #[inline] + fn deserialize_i8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_i8(self.into_int()? as i8) + } + + #[inline] + fn deserialize_i16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_i16(self.into_int()? as i16) + } + + #[inline] + fn deserialize_i32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_i32(self.into_int()? as i32) + } + + #[inline] + fn deserialize_i64>(self, visitor: V) -> Result { + visitor.visit_i64(self.into_int()?) + } + + #[inline] + fn deserialize_u8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u8(self.into_int()? as u8) + } + + #[inline] + fn deserialize_u16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u16(self.into_int()? as u16) + } + + #[inline] + fn deserialize_u32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u32(self.into_int()? as u32) + } + + #[inline] + fn deserialize_u64>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u64(self.into_int()? as u64) + } + + #[inline] + fn deserialize_f32>(self, visitor: V) -> Result { + visitor.visit_f32(self.into_float()? as f32) + } + + #[inline] + fn deserialize_f64>(self, visitor: V) -> Result { + visitor.visit_f64(self.into_float()?) + } + + #[inline] + fn deserialize_str>(self, visitor: V) -> Result { + visitor.visit_string(self.into_str()?) + } + + #[inline] + fn deserialize_string>(self, visitor: V) -> Result { + visitor.visit_string(self.into_str()?) + } + + #[inline] + fn deserialize_option(self, visitor: V) -> Result + where V: de::Visitor<'de> + { + // Match an explicit nil as None and everything else as Some + match self.0.kind { + ValueKind::Nil => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + forward_to_deserialize_any! { + char seq + bytes byte_buf map struct unit enum newtype_struct + identifier ignored_any unit_struct tuple_struct tuple + } +} + +impl<'de> de::Deserializer<'de> for Value { + type Error = ConfigError; + + #[inline] + fn deserialize_any(self, visitor: V) -> Result + where V: de::Visitor<'de> { // Deserialize based on the underlying type match self.kind { + ValueKind::Nil => visitor.visit_unit(), ValueKind::Integer(i) => visitor.visit_i64(i), ValueKind::Boolean(b) => visitor.visit_bool(b), ValueKind::Float(f) => visitor.visit_f64(f), ValueKind::String(s) => visitor.visit_string(s), - ValueKind::Array(values) => unimplemented!(), - ValueKind::Table(map) => visitor.visit_map(MapVisitor::new(map)), - _ => { - unimplemented!(); - } + ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), + ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), } } #[inline] - fn deserialize_bool(self, visitor: V) -> Result { + fn deserialize_bool>(self, visitor: V) -> Result { visitor.visit_bool(self.into_bool()?) } #[inline] - fn deserialize_i8(self, visitor: V) -> Result { + fn deserialize_i8>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_i8(self.into_int()? as i8) } #[inline] - fn deserialize_i16(self, visitor: V) -> Result { + fn deserialize_i16>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_i16(self.into_int()? as i16) } #[inline] - fn deserialize_i32(self, visitor: V) -> Result { + fn deserialize_i32>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_i32(self.into_int()? as i32) } #[inline] - fn deserialize_i64(self, visitor: V) -> Result { + fn deserialize_i64>(self, visitor: V) -> Result { visitor.visit_i64(self.into_int()?) } #[inline] - fn deserialize_u8(self, visitor: V) -> Result { + fn deserialize_u8>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_u8(self.into_int()? as u8) } #[inline] - fn deserialize_u16(self, visitor: V) -> Result { + fn deserialize_u16>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_u16(self.into_int()? as u16) } #[inline] - fn deserialize_u32(self, visitor: V) -> Result { + fn deserialize_u32>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_u32(self.into_int()? as u32) } #[inline] - fn deserialize_u64(self, visitor: V) -> Result { + fn deserialize_u64>(self, visitor: V) -> Result { // FIXME: This should *fail* if the value does not fit in the requets integer type visitor.visit_u64(self.into_int()? as u64) } #[inline] - fn deserialize_f32(self, visitor: V) -> Result { + fn deserialize_f32>(self, visitor: V) -> Result { visitor.visit_f32(self.into_float()? as f32) } #[inline] - fn deserialize_f64(self, visitor: V) -> Result { + fn deserialize_f64>(self, visitor: V) -> Result { visitor.visit_f64(self.into_float()?) } #[inline] - fn deserialize_str(self, visitor: V) -> Result { + fn deserialize_str>(self, visitor: V) -> Result { visitor.visit_string(self.into_str()?) } #[inline] - fn deserialize_string(self, visitor: V) -> Result { + fn deserialize_string>(self, visitor: V) -> Result { visitor.visit_string(self.into_str()?) } #[inline] fn deserialize_option(self, visitor: V) -> Result - where V: de::Visitor + where V: de::Visitor<'de> { // Match an explicit nil as None and everything else as Some match self.kind { @@ -110,10 +219,10 @@ impl de::Deserializer for Value { } } - forward_to_deserialize! { + forward_to_deserialize_any! { char seq - seq_fixed_size bytes byte_buf map struct unit enum newtype_struct - struct_field ignored_any unit_struct tuple_struct tuple + bytes byte_buf map struct unit enum newtype_struct + identifier ignored_any unit_struct tuple_struct tuple } } @@ -125,54 +234,86 @@ impl<'a> StrDeserializer<'a> { } } -impl<'a> de::Deserializer for StrDeserializer<'a> { +impl<'de, 'a> de::Deserializer<'de> for StrDeserializer<'a> { type Error = ConfigError; #[inline] - fn deserialize(self, visitor: V) -> Result { + fn deserialize_any>(self, visitor: V) -> Result { visitor.visit_str(self.0) } - forward_to_deserialize! { + forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - seq_fixed_size bytes byte_buf map struct unit enum newtype_struct - struct_field ignored_any unit_struct tuple_struct tuple option + bytes byte_buf map struct unit enum newtype_struct + identifier ignored_any unit_struct tuple_struct tuple option + } +} + +struct SeqAccess { + elements: ::std::vec::IntoIter, +} + +impl SeqAccess { + fn new(elements: Vec) -> Self { + SeqAccess { + elements: elements.into_iter(), + } + } +} + +impl<'de> de::SeqAccess<'de> for SeqAccess { + type Error = ConfigError; + + fn next_element_seed(&mut self, seed: T) -> Result> + where T: de::DeserializeSeed<'de> + { + match self.elements.next() { + Some(value) => seed.deserialize(value).map(Some), + None => Ok(None), + } + } + + fn size_hint(&self) -> Option { + match self.elements.size_hint() { + (lower, Some(upper)) if lower == upper => Some(upper), + _ => None, + } } } -struct MapVisitor { +struct MapAccess { elements: Vec<(String, Value)>, index: usize, } -impl MapVisitor { +impl MapAccess { fn new(mut table: HashMap) -> Self { - MapVisitor { + MapAccess { elements: table.drain().collect(), index: 0, } } } -impl de::MapVisitor for MapVisitor { +impl<'de> de::MapAccess<'de> for MapAccess { type Error = ConfigError; - fn visit_key_seed(&mut self, seed: K) -> Result> - where K: de::DeserializeSeed + fn next_key_seed(&mut self, seed: K) -> Result> + where K: de::DeserializeSeed<'de> { if self.index >= self.elements.len() { return Ok(None); } - let key_s = &self.elements[0].0; + let key_s = &(self.elements[0].0); let key_de = StrDeserializer(key_s); let key = de::DeserializeSeed::deserialize(seed, key_de)?; Ok(Some(key)) } - fn visit_value_seed(&mut self, seed: V) -> Result - where V: de::DeserializeSeed + fn next_value_seed(&mut self, seed: V) -> Result + where V: de::DeserializeSeed<'de> { de::DeserializeSeed::deserialize(seed, self.elements.remove(0).1) } diff --git a/src/error.rs b/src/error.rs index 8af5cee..92709c2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -50,12 +50,13 @@ pub enum ConfigError { origin: Option, unexpected: Unexpected, expected: &'static str, + key: Option, }, /// Custom message Message(String), - /// Unadorned error from a foreign source. + /// Unadorned error from a foreign origin. Foreign(Box), } @@ -66,9 +67,27 @@ impl ConfigError { ConfigError::Type { origin: origin, unexpected: unexpected, - expected: expected + expected: expected, + key: None, } } + + // FIXME: pub(crate) + #[doc(hidden)] + pub fn extend_with_key(self, key: &str) -> Self { + match self { + ConfigError::Type { origin, unexpected, expected, .. } => { + ConfigError::Type { + origin: origin, + unexpected: unexpected, + expected: expected, + key: Some(key.into()), + } + } + + _ => self, + } + } } /// Alias for a `Result` with the error type set to `ConfigError`. @@ -100,12 +119,16 @@ impl fmt::Display for ConfigError { write!(f, "configuration property {:?} not found", key) } - ConfigError::Type { ref origin, ref unexpected, expected } => { + ConfigError::Type { ref origin, ref unexpected, expected, ref key } => { write!(f, "invalid type: {}, expected {}", unexpected, expected)?; + if let Some(ref key) = *key { + write!(f, " for key `{}`", key)?; + } + if let Some(ref origin) = *origin { - write!(f, " from {}", origin)?; + write!(f, " in {}", origin)?; } Ok(()) @@ -115,7 +138,7 @@ impl fmt::Display for ConfigError { write!(f, "{}", cause)?; if let Some(ref uri) = *uri { - write!(f, " from {}", uri)?; + write!(f, " in {}", uri)?; } Ok(()) -- cgit v1.2.3