From eff2ed3b51fecf4e6799690c09ce35daa6e51280 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Wed, 10 Oct 2018 08:59:46 +1300 Subject: Implement deserialization for unit and struct variants. Issue #225 --- src/de.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 491ae47..5de4f6a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -585,7 +585,27 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { { match self.value.e { E::String(val) => visitor.visit_enum(val.into_deserializer()), - _ => Err(Error::from_kind(ErrorKind::ExpectedString)) + E::InlineTable(values) | E::DottedTable(values) => { + if values.len() != 1 { + Err(Error::from_kind(ErrorKind::Wanted { + expected: "exactly 1 element", + found: if values.is_empty() { + "zero elements" + } else { + "more than 1 element" + }, + })) + } else { + visitor.visit_enum(InlineTableDeserializer { + values: values.into_iter(), + next_value: None, + }) + } + } + e @ _ => Err(Error::from_kind(ErrorKind::Wanted { + expected: "string or table", + found: e.type_name(), + })), } } @@ -724,6 +744,70 @@ impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> { } } +impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { + type Error = Error; + type Variant = Self; + + fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: de::DeserializeSeed<'de>, + { + let (key, value) = match self.values.next() { + Some(pair) => pair, + None => { + return Err(Error::from_kind(ErrorKind::Wanted { + expected: "table with exactly 1 entry", + found: "empty map", + })) + } + }; + self.next_value = Some(value); + + seed.deserialize(StrDeserializer::new(key)) + .map(|val| (val, self)) + } +} + +impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + // TODO: Error handling if there are entries + Ok(()) + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + seed.deserialize(ValueDeserializer::new( + self.next_value.expect("Expected value"), + )) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + unimplemented!() + } + + fn struct_variant( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + de::Deserializer::deserialize_struct( + ValueDeserializer::new(self.next_value.expect("Expected value")), + "", // TODO: this should be the variant name + fields, + visitor, + ) + } +} impl<'a> Deserializer<'a> { /// Creates a new deserializer which will be deserializing the string @@ -1510,6 +1594,21 @@ enum E<'a> { DottedTable(Vec<(Cow<'a, str>, Value<'a>)>), } +impl<'a> E<'a> { + fn type_name(&self) -> &'static str { + match *self { + E::String(..) => "string", + E::Integer(..) => "integer", + E::Float(..) => "float", + E::Boolean(..) => "boolean", + E::Datetime(..) => "datetime", + E::Array(..) => "array", + E::InlineTable(..) => "inline table", + E::DottedTable(..) => "dotted table", + } + } +} + impl<'a> Value<'a> { fn same_type(&self, other: &Value<'a>) -> bool { match (&self.e, &other.e) { -- cgit v1.2.3 From 9ce37a6b0278d4c422e5cac7003cf908cc2c9e3f Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Wed, 10 Oct 2018 11:36:46 +1300 Subject: Implemented enum tuple deserialization. Issue #225 --- src/de.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 6 deletions(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 5de4f6a..4f5a5f2 100644 --- a/src/de.rs +++ b/src/de.rs @@ -162,6 +162,12 @@ enum ErrorKind { /// A struct was expected but something else was found ExpectedString, + /// A tuple with a certain number of elements was expected but something else was found + ExpectedTuple(usize), + + /// An empty table was expected but entries were found + ExpectedEmptyTable, + /// Dotted key attempted to extend something that is not a table. DottedKeyInvalidType, @@ -757,7 +763,7 @@ impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { None => { return Err(Error::from_kind(ErrorKind::Wanted { expected: "table with exactly 1 entry", - found: "empty map", + found: "empty table", })) } }; @@ -772,8 +778,19 @@ impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { type Error = Error; fn unit_variant(self) -> Result<(), Self::Error> { - // TODO: Error handling if there are entries - Ok(()) + match self.next_value.expect("Expected value").e { + E::InlineTable(values) | E::DottedTable(values) => { + if values.len() == 0 { + Ok(()) + } else { + Err(Error::from_kind(ErrorKind::ExpectedEmptyTable)) + } + } + e @ _ => Err(Error::from_kind(ErrorKind::Wanted { + expected: "table", + found: e.type_name(), + })), + } } fn newtype_variant_seed(self, seed: T) -> Result @@ -785,11 +802,45 @@ impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { )) } - fn tuple_variant(self, _len: usize, _visitor: V) -> Result + fn tuple_variant(self, len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { - unimplemented!() + let next_value = self.next_value.expect("Expected value"); + match next_value.e { + E::InlineTable(values) | E::DottedTable(values) => { + let tuple_values = values + .into_iter() + .enumerate() + .filter_map(|(index, (key, value))| { + // TODO: Is this expensive? + if key == format!("{}", index) { + Some(value) + } else { + // TODO: Err() + unimplemented!() + } + }) + .collect::>(); // TODO: is this expensive? + + if tuple_values.len() == len { + de::Deserializer::deserialize_seq( + ValueDeserializer::new(Value { + e: E::Array(tuple_values), + start: next_value.start, + end: next_value.end, + }), + visitor, + ) + } else { + Err(Error::from_kind(ErrorKind::ExpectedTuple(len))) + } + } + e @ _ => Err(Error::from_kind(ErrorKind::Wanted { + expected: "table", + found: e.type_name(), + })), + } } fn struct_variant( @@ -1470,7 +1521,11 @@ impl fmt::Display for Error { ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?, ErrorKind::Custom => self.inner.message.fmt(f)?, ErrorKind::ExpectedString => "expected string".fmt(f)?, - ErrorKind::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?, + ErrorKind::ExpectedTuple(l) => write!(f, "expected tuple with length {}", l)?, + ErrorKind::ExpectedEmptyTable => "expected empty table".fmt(f)?, + ErrorKind::DottedKeyInvalidType => { + "dotted key attempted to extend non-table type".fmt(f)? + } ErrorKind::__Nonexhaustive => panic!(), } @@ -1515,6 +1570,8 @@ impl error::Error for Error { ErrorKind::MultilineStringKey => "invalid multiline string for key", ErrorKind::Custom => "a custom error", ErrorKind::ExpectedString => "expected string", + ErrorKind::ExpectedTuple(_) => "expected tuple", + ErrorKind::ExpectedEmptyTable => "expected empty table", ErrorKind::DottedKeyInvalidType => "dotted key invalid type", ErrorKind::__Nonexhaustive => panic!(), } -- cgit v1.2.3 From 8fc8a6a5ca872022b9412003a7ce90f0dce76f9c Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Sat, 20 Oct 2018 16:24:16 +1300 Subject: Better error handling when parsing tuples. Issue #225 --- src/de.rs | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 4f5a5f2..810b85b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -162,9 +162,19 @@ enum ErrorKind { /// A struct was expected but something else was found ExpectedString, - /// A tuple with a certain number of elements was expected but something else was found + /// A tuple with a certain number of elements was expected but something + /// else was found. ExpectedTuple(usize), + /// Expected table keys to be in increasing tuple index order, but something + /// else was found. + ExpectedTupleIndex { + /// Expected index. + expected: usize, + /// Key that was specified. + found: String, + }, + /// An empty table was expected but entries were found ExpectedEmptyTable, @@ -812,16 +822,24 @@ impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { let tuple_values = values .into_iter() .enumerate() - .filter_map(|(index, (key, value))| { - // TODO: Is this expensive? - if key == format!("{}", index) { - Some(value) - } else { - // TODO: Err() - unimplemented!() - } + .map(|(index, (key, value))| match key.parse::() { + Ok(key_index) if key_index == index => Ok(value), + Ok(_) | Err(_) => Err(Error::from_kind(ErrorKind::ExpectedTupleIndex { + expected: index, + found: key.to_string(), + })), }) - .collect::>(); // TODO: is this expensive? + // Fold all values into a `Vec`, or return the first error. + .fold(Ok(Vec::with_capacity(len)), |result, value_result| { + result.and_then(|mut tuple_values| match value_result { + Ok(value) => { + tuple_values.push(value); + Ok(tuple_values) + } + // `Result` to `Result, Self::Error>` + Err(e) => Err(e), + }) + })?; if tuple_values.len() == len { de::Deserializer::deserialize_seq( @@ -1521,7 +1539,11 @@ impl fmt::Display for Error { ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?, ErrorKind::Custom => self.inner.message.fmt(f)?, ErrorKind::ExpectedString => "expected string".fmt(f)?, - ErrorKind::ExpectedTuple(l) => write!(f, "expected tuple with length {}", l)?, + ErrorKind::ExpectedTuple(l) => write!(f, "expected table with length {}", l)?, + ErrorKind::ExpectedTupleIndex { + expected, + ref found, + } => write!(f, "expected table key `{}`, but was `{}`", expected, found)?, ErrorKind::ExpectedEmptyTable => "expected empty table".fmt(f)?, ErrorKind::DottedKeyInvalidType => { "dotted key attempted to extend non-table type".fmt(f)? @@ -1570,7 +1592,8 @@ impl error::Error for Error { ErrorKind::MultilineStringKey => "invalid multiline string for key", ErrorKind::Custom => "a custom error", ErrorKind::ExpectedString => "expected string", - ErrorKind::ExpectedTuple(_) => "expected tuple", + ErrorKind::ExpectedTuple(_) => "expected table length", + ErrorKind::ExpectedTupleIndex { .. } => "expected table key", ErrorKind::ExpectedEmptyTable => "expected empty table", ErrorKind::DottedKeyInvalidType => "dotted key invalid type", ErrorKind::__Nonexhaustive => panic!(), -- cgit v1.2.3 From fcc4a58617ea27a061f445b576e3fafc91ad6998 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 22 Oct 2018 09:40:16 +1300 Subject: Implemented deserialization for external enums. Issue #225 --- src/de.rs | 246 ++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 175 insertions(+), 71 deletions(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 810b85b..7fd1387 100644 --- a/src/de.rs +++ b/src/de.rs @@ -159,9 +159,6 @@ enum ErrorKind { /// type. Custom, - /// A struct was expected but something else was found - ExpectedString, - /// A tuple with a certain number of elements was expected but something /// else was found. ExpectedTuple(usize), @@ -198,47 +195,8 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { - let mut tables = Vec::new(); - let mut cur_table = Table { - at: 0, - header: Vec::new(), - values: None, - array: false, - }; - while let Some(line) = self.line()? { - match line { - Line::Table { at, mut header, array } => { - if !cur_table.header.is_empty() || cur_table.values.is_some() { - tables.push(cur_table); - } - cur_table = Table { - at: at, - header: Vec::new(), - values: Some(Vec::new()), - array: array, - }; - loop { - let part = header.next().map_err(|e| { - self.token_error(e) - }); - match part? { - Some(part) => cur_table.header.push(part), - None => break, - } - } - } - Line::KeyValue(key, value) => { - if cur_table.values.is_none() { - cur_table.values = Some(Vec::new()); - } - self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?; - } - } - } - if !cur_table.header.is_empty() || cur_table.values.is_some() { - tables.push(cur_table); - } + let mut tables = self.tables()?; visitor.visit_map(MapVisitor { values: Vec::new().into_iter(), @@ -253,6 +211,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { }) } + // Called when the type to deserialize is an enum, as opposed to a field in the type. fn deserialize_enum( self, _name: &'static str, @@ -261,15 +220,34 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { ) -> Result where V: de::Visitor<'de> { - if let Some(next) = self.next()? { - match next { - (_, Token::String { val, .. }) => { - visitor.visit_enum(val.into_deserializer()) - }, - _ => Err(Error::from_kind(ErrorKind::ExpectedString)) + let (value, name) = self.string_or_table()?; + match value.e { + E::String(val) => visitor.visit_enum(val.into_deserializer()), + E::InlineTable(values) => { + if values.len() != 1 { + Err(Error::from_kind(ErrorKind::Wanted { + expected: "exactly 1 element", + found: if values.is_empty() { + "zero elements" + } else { + "more than 1 element" + }, + })) + } else { + visitor.visit_enum(InlineTableDeserializer { + values: values.into_iter(), + next_value: None, + }) + } } - } else { - Err(Error::from_kind(ErrorKind::UnexpectedEof)) + E::DottedTable(_) => visitor.visit_enum(DottedTableDeserializer { + name: name.expect("Expected table header to be passed."), + value: value, + }), + e @ _ => Err(Error::from_kind(ErrorKind::Wanted { + expected: "string or table", + found: e.type_name(), + })), } } @@ -601,7 +579,7 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { { match self.value.e { E::String(val) => visitor.visit_enum(val.into_deserializer()), - E::InlineTable(values) | E::DottedTable(values) => { + E::InlineTable(values) => { if values.len() != 1 { Err(Error::from_kind(ErrorKind::Wanted { expected: "exactly 1 element", @@ -619,7 +597,7 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { } } e @ _ => Err(Error::from_kind(ErrorKind::Wanted { - expected: "string or table", + expected: "string or inline table", found: e.type_name(), })), } @@ -733,6 +711,25 @@ impl<'de> de::Deserializer<'de> for DatetimeFieldDeserializer { } } +struct DottedTableDeserializer<'a> { + name: Cow<'a, str>, + value: Value<'a>, +} + +impl<'de> de::EnumAccess<'de> for DottedTableDeserializer<'de> { + type Error = Error; + type Variant = TableEnumDeserializer<'de>; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: de::DeserializeSeed<'de>, + { + let (name, value) = (self.name, self.value); + seed.deserialize(StrDeserializer::new(name)) + .map(|val| (val, TableEnumDeserializer { value: value })) + } +} + struct InlineTableDeserializer<'a> { values: vec::IntoIter<(Cow<'a, str>, Value<'a>)>, next_value: Option>, @@ -762,7 +759,7 @@ impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> { impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { type Error = Error; - type Variant = Self; + type Variant = TableEnumDeserializer<'de>; fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> where @@ -777,18 +774,22 @@ impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { })) } }; - self.next_value = Some(value); seed.deserialize(StrDeserializer::new(key)) - .map(|val| (val, self)) + .map(|val| (val, TableEnumDeserializer { value: value })) } } -impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { +/// Deserializes table values into enum variants. +struct TableEnumDeserializer<'a> { + value: Value<'a>, +} + +impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { type Error = Error; fn unit_variant(self) -> Result<(), Self::Error> { - match self.next_value.expect("Expected value").e { + match self.value.e { E::InlineTable(values) | E::DottedTable(values) => { if values.len() == 0 { Ok(()) @@ -807,17 +808,14 @@ impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { where T: de::DeserializeSeed<'de>, { - seed.deserialize(ValueDeserializer::new( - self.next_value.expect("Expected value"), - )) + seed.deserialize(ValueDeserializer::new(self.value)) } fn tuple_variant(self, len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { - let next_value = self.next_value.expect("Expected value"); - match next_value.e { + match self.value.e { E::InlineTable(values) | E::DottedTable(values) => { let tuple_values = values .into_iter() @@ -845,8 +843,8 @@ impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { de::Deserializer::deserialize_seq( ValueDeserializer::new(Value { e: E::Array(tuple_values), - start: next_value.start, - end: next_value.end, + start: self.value.start, + end: self.value.end, }), visitor, ) @@ -870,7 +868,7 @@ impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { V: de::Visitor<'de>, { de::Deserializer::deserialize_struct( - ValueDeserializer::new(self.next_value.expect("Expected value")), + ValueDeserializer::new(self.value), "", // TODO: this should be the variant name fields, visitor, @@ -907,6 +905,53 @@ impl<'a> Deserializer<'a> { self.require_newline_after_table = require; } + fn tables(&mut self) -> Result>, Error> { + let mut tables = Vec::new(); + let mut cur_table = Table { + at: 0, + header: Vec::new(), + values: None, + array: false, + }; + + while let Some(line) = self.line()? { + match line { + Line::Table { + at, + mut header, + array, + } => { + if !cur_table.header.is_empty() || cur_table.values.is_some() { + tables.push(cur_table); + } + cur_table = Table { + at: at, + header: Vec::new(), + values: Some(Vec::new()), + array: array, + }; + loop { + let part = header.next().map_err(|e| self.token_error(e)); + match part? { + Some(part) => cur_table.header.push(part), + None => break, + } + } + } + Line::KeyValue(key, value) => { + if cur_table.values.is_none() { + cur_table.values = Some(Vec::new()); + } + self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?; + } + } + } + if !cur_table.header.is_empty() || cur_table.values.is_some() { + tables.push(cur_table); + } + Ok(tables) + } + fn line(&mut self) -> Result>, Error> { loop { self.eat_whitespace()?; @@ -1029,7 +1074,56 @@ impl<'a> Deserializer<'a> { } } - fn number(&mut self, Span { start, end}: Span, s: &'a str) -> Result, Error> { + /// Returns a string or table value type. + /// + /// Used to deserialize enums. Unit enums may be represented as a string or a table, all other + /// structures (tuple, newtype, struct) must be represented as a table. + fn string_or_table(&mut self) -> Result<(Value<'a>, Option>), Error> { + match self.peek()? { + Some((_, Token::LeftBracket)) => { + let tables = self.tables()?; + if tables.len() != 1 { + return Err(Error::from_kind(ErrorKind::Wanted { + expected: "exactly 1 table", + found: if tables.is_empty() { + "zero tables" + } else { + "more than 1 table" + }, + })); + } + + let table = tables + .into_iter() + .next() + .expect("Expected exactly one table"); + let header = table + .header + .last() + .expect("Expected at least one header value for table."); + + let start = table.at; + let end = table + .values + .as_ref() + .and_then(|values| values.last()) + .map(|&(_, ref val)| val.end) + .unwrap_or_else(|| header.len()); + Ok(( + Value { + e: E::DottedTable(table.values.unwrap_or_else(Vec::new)), + start: start, + end: end, + }, + Some(header.clone()), + )) + } + Some(_) => self.value().map(|val| (val, None)), + None => Err(self.eof()), + } + } + + fn number(&mut self, Span { start, end }: Span, s: &'a str) -> Result, Error> { let to_integer = |f| Value { e: E::Integer(f), start: start, end: end }; if s.starts_with("0x") { self.integer(&s[2..], 16).map(to_integer) @@ -1324,6 +1418,18 @@ impl<'a> Deserializer<'a> { Ok(result) } + /// Stores a value in the appropriate hierachical structure positioned based on the dotted key. + /// + /// Given the following definition: `multi.part.key = "value"`, `multi` and `part` are + /// intermediate parts which are mapped to the relevant fields in the deserialized type's data + /// hierarchy. + /// + /// # Parameters + /// + /// * `key_parts`: Each segment of the dotted key, e.g. `part.one` maps to + /// `vec![Cow::Borrowed("part"), Cow::Borrowed("one")].` + /// * `value`: The parsed value. + /// * `values`: The `Vec` to store the value in. fn add_dotted_key( &self, mut key_parts: Vec>, @@ -1345,12 +1451,12 @@ impl<'a> Deserializer<'a> { None => {} } // The start/end value is somewhat misleading here. - let inline_table = Value { + let table_values = Value { e: E::DottedTable(Vec::new()), start: value.start, end: value.end, }; - values.push((key, inline_table)); + values.push((key, table_values)); let last_i = values.len() - 1; if let (_, Value { e: E::DottedTable(ref mut v), .. }) = values[last_i] { self.add_dotted_key(key_parts, value, v)?; @@ -1538,7 +1644,6 @@ impl fmt::Display for Error { ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?, ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?, ErrorKind::Custom => self.inner.message.fmt(f)?, - ErrorKind::ExpectedString => "expected string".fmt(f)?, ErrorKind::ExpectedTuple(l) => write!(f, "expected table with length {}", l)?, ErrorKind::ExpectedTupleIndex { expected, @@ -1591,7 +1696,6 @@ impl error::Error for Error { ErrorKind::EmptyTableKey => "empty table key found", ErrorKind::MultilineStringKey => "invalid multiline string for key", ErrorKind::Custom => "a custom error", - ErrorKind::ExpectedString => "expected string", ErrorKind::ExpectedTuple(_) => "expected table length", ErrorKind::ExpectedTupleIndex { .. } => "expected table key", ErrorKind::ExpectedEmptyTable => "expected empty table", -- cgit v1.2.3 From 00ee52194039594e2e842c4448ad952cbd6103da Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 22 Oct 2018 10:33:20 +1300 Subject: Maintain compatibility with Rust 1.15 Issue #225 --- src/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 7fd1387..da30699 100644 --- a/src/de.rs +++ b/src/de.rs @@ -829,7 +829,7 @@ impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { }) // Fold all values into a `Vec`, or return the first error. .fold(Ok(Vec::with_capacity(len)), |result, value_result| { - result.and_then(|mut tuple_values| match value_result { + result.and_then(move |mut tuple_values| match value_result { Ok(value) => { tuple_values.push(value); Ok(tuple_values) -- cgit v1.2.3 From 2907bd1953b15c81d4e7bfd1b060a9c9d947c2e4 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 12 Nov 2018 10:09:30 +1300 Subject: Error when deserializing struct if encountering unknown fields. Issue #225 --- src/de.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index da30699..c7e8b34 100644 --- a/src/de.rs +++ b/src/de.rs @@ -178,6 +178,16 @@ enum ErrorKind { /// Dotted key attempted to extend something that is not a table. DottedKeyInvalidType, + /// An unexpected key was encountered. + /// + /// Used when deserializing a struct with a limited set of fields. + UnexpectedKeys { + /// The unexpected keys. + keys: Vec, + /// Keys that may be specified. + available: &'static [&'static str], + }, + #[doc(hidden)] __Nonexhaustive, } @@ -547,6 +557,28 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { } } + match &self.value.e { + E::InlineTable(values) | E::DottedTable(values) => { + let extra_fields = values.iter() + .filter_map(|(key, _val)| { + if !fields.contains(&&(**key)) { + Some(key.clone()) + } else { + None + } + }) + .collect::>>(); + + if !extra_fields.is_empty() { + return Err(Error::from_kind(ErrorKind::UnexpectedKeys { + keys: extra_fields.iter().map(|k| k.to_string()).collect::>(), + available: fields, + })); + } + } + _ => {} + } + if name == spanned::NAME && fields == &[spanned::START, spanned::END, spanned::VALUE] { let start = self.value.start; let end = self.value.end; @@ -1653,6 +1685,14 @@ impl fmt::Display for Error { ErrorKind::DottedKeyInvalidType => { "dotted key attempted to extend non-table type".fmt(f)? } + ErrorKind::UnexpectedKeys { ref keys, available } => { + write!( + f, + "unexpected keys in table: `{:?}`, available keys: `{:?}`", + keys, + available + )? + } ErrorKind::__Nonexhaustive => panic!(), } @@ -1700,6 +1740,7 @@ impl error::Error for Error { ErrorKind::ExpectedTupleIndex { .. } => "expected table key", ErrorKind::ExpectedEmptyTable => "expected empty table", ErrorKind::DottedKeyInvalidType => "dotted key invalid type", + ErrorKind::UnexpectedKeys { .. } => "unexpected keys in table", ErrorKind::__Nonexhaustive => panic!(), } } -- cgit v1.2.3 From 45ab620a59443dc6c3e6dae19bf5c09c222f21d4 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Sat, 17 Nov 2018 09:18:43 +1300 Subject: Maintain compatibility with Rust 1.15 Issue #225 --- src/de.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index c7e8b34..869f7ae 100644 --- a/src/de.rs +++ b/src/de.rs @@ -558,9 +558,9 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { } match &self.value.e { - E::InlineTable(values) | E::DottedTable(values) => { + &E::InlineTable(ref values) | &E::DottedTable(ref values) => { let extra_fields = values.iter() - .filter_map(|(key, _val)| { + .filter_map(|(ref key, ref _val)| { if !fields.contains(&&(**key)) { Some(key.clone()) } else { -- cgit v1.2.3 From 438157e662d2092dd49513a9791aa75788180c35 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Sat, 17 Nov 2018 09:37:02 +1300 Subject: Only validate table keys against fields when deserializing enum. Issue #225 --- src/de.rs | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 869f7ae..9d5a417 100644 --- a/src/de.rs +++ b/src/de.rs @@ -501,14 +501,21 @@ impl<'de> de::Deserializer<'de> for StrDeserializer<'de> { struct ValueDeserializer<'a> { value: Value<'a>, + validate_struct_keys: bool, } impl<'a> ValueDeserializer<'a> { fn new(value: Value<'a>) -> ValueDeserializer<'a> { ValueDeserializer { value: value, + validate_struct_keys: false, } } + + fn with_struct_key_validation(mut self) -> Self { + self.validate_struct_keys = true; + self + } } impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { @@ -557,26 +564,28 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { } } - match &self.value.e { - &E::InlineTable(ref values) | &E::DottedTable(ref values) => { - let extra_fields = values.iter() - .filter_map(|(ref key, ref _val)| { - if !fields.contains(&&(**key)) { - Some(key.clone()) - } else { - None - } - }) - .collect::>>(); + if self.validate_struct_keys { + match &self.value.e { + &E::InlineTable(ref values) | &E::DottedTable(ref values) => { + let extra_fields = values.iter() + .filter_map(|(ref key, ref _val)| { + if !fields.contains(&&(**key)) { + Some(key.clone()) + } else { + None + } + }) + .collect::>>(); - if !extra_fields.is_empty() { - return Err(Error::from_kind(ErrorKind::UnexpectedKeys { - keys: extra_fields.iter().map(|k| k.to_string()).collect::>(), - available: fields, - })); + if !extra_fields.is_empty() { + return Err(Error::from_kind(ErrorKind::UnexpectedKeys { + keys: extra_fields.iter().map(|k| k.to_string()).collect::>(), + available: fields, + })); + } } + _ => {} } - _ => {} } if name == spanned::NAME && fields == &[spanned::START, spanned::END, spanned::VALUE] { @@ -900,7 +909,7 @@ impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { V: de::Visitor<'de>, { de::Deserializer::deserialize_struct( - ValueDeserializer::new(self.value), + ValueDeserializer::new(self.value).with_struct_key_validation(), "", // TODO: this should be the variant name fields, visitor, -- cgit v1.2.3 From df7ec3a4b66433118f09b12d5f32a10368a57852 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Sat, 17 Nov 2018 13:47:23 +1300 Subject: Maintain backward compatibility with Rust 1.15, take 3. --- src/de.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/de.rs') diff --git a/src/de.rs b/src/de.rs index 9d5a417..9b58606 100644 --- a/src/de.rs +++ b/src/de.rs @@ -568,7 +568,8 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { match &self.value.e { &E::InlineTable(ref values) | &E::DottedTable(ref values) => { let extra_fields = values.iter() - .filter_map(|(ref key, ref _val)| { + .filter_map(|key_value| { + let (ref key, ref _val) = *key_value; if !fields.contains(&&(**key)) { Some(key.clone()) } else { -- cgit v1.2.3