aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAzriel Hoh <azriel91@gmail.com>2018-10-22 09:40:16 +1300
committerAzriel Hoh <azriel91@gmail.com>2018-11-10 11:10:31 +1300
commitfcc4a58617ea27a061f445b576e3fafc91ad6998 (patch)
treea8cbe76b5a68ebed0c42e42fbd3b40d366b557ad
parent8fc8a6a5ca872022b9412003a7ce90f0dce76f9c (diff)
downloadmilf-rs-fcc4a58617ea27a061f445b576e3fafc91ad6998.tar.gz
milf-rs-fcc4a58617ea27a061f445b576e3fafc91ad6998.zip
Implemented deserialization for external enums.
Issue #225
-rw-r--r--src/de.rs246
-rw-r--r--tests/enum_external_deserialize.rs153
2 files changed, 328 insertions, 71 deletions
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<V>(self, visitor: V) -> Result<V::Value, Error>
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<V>(
self,
_name: &'static str,
@@ -261,15 +220,34 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
) -> Result<V::Value, Error>
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<V>(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<Value<'a>>,
@@ -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<V>(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<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
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<Vec<Table<'a>>, 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<Option<Line<'a>>, 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<Value<'a>, 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<Cow<'a, str>>), 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<Value<'a>, 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<Cow<'a, str>>,
@@ -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",
diff --git a/tests/enum_external_deserialize.rs b/tests/enum_external_deserialize.rs
new file mode 100644
index 0000000..76e7a4c
--- /dev/null
+++ b/tests/enum_external_deserialize.rs
@@ -0,0 +1,153 @@
+#[macro_use]
+extern crate serde_derive;
+extern crate toml;
+
+#[derive(Debug, Deserialize, PartialEq)]
+enum TheEnum {
+ Plain,
+ Tuple(i64, bool),
+ NewType(String),
+ Struct { value: i64 },
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Val {
+ val: TheEnum,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Multi {
+ enums: Vec<TheEnum>,
+}
+
+mod enum_unit {
+ use super::*;
+
+ #[test]
+ fn from_str() {
+ assert_eq!(TheEnum::Plain, toml::from_str("\"Plain\"").unwrap());
+ }
+
+ #[test]
+ fn from_inline_table() {
+ assert_eq!(TheEnum::Plain, toml::from_str("{ Plain = {} }").unwrap());
+ assert_eq!(
+ Val {
+ val: TheEnum::Plain
+ },
+ toml::from_str("val = { Plain = {} }").unwrap()
+ );
+ }
+
+ #[test]
+ fn from_dotted_table() {
+ assert_eq!(TheEnum::Plain, toml::from_str("[Plain]\n").unwrap());
+ }
+}
+
+mod enum_tuple {
+ use super::*;
+
+ #[test]
+ fn from_inline_table() {
+ assert_eq!(
+ TheEnum::Tuple(-123, true),
+ toml::from_str("{ Tuple = { 0 = -123, 1 = true } }").unwrap()
+ );
+ assert_eq!(
+ Val {
+ val: TheEnum::Tuple(-123, true)
+ },
+ toml::from_str("val = { Tuple = { 0 = -123, 1 = true } }").unwrap()
+ );
+ }
+
+ #[test]
+ fn from_dotted_table() {
+ assert_eq!(
+ TheEnum::Tuple(-123, true),
+ toml::from_str(
+ r#"[Tuple]
+ 0 = -123
+ 1 = true
+ "#
+ )
+ .unwrap()
+ );
+ }
+}
+
+mod enum_newtype {
+ use super::*;
+
+ #[test]
+ fn from_inline_table() {
+ assert_eq!(
+ TheEnum::NewType("value".to_string()),
+ toml::from_str(r#"{ NewType = "value" }"#).unwrap()
+ );
+ assert_eq!(
+ Val {
+ val: TheEnum::NewType("value".to_string()),
+ },
+ toml::from_str(r#"val = { NewType = "value" }"#).unwrap()
+ );
+ }
+}
+
+mod enum_struct {
+ use super::*;
+
+ #[test]
+ fn from_inline_table() {
+ assert_eq!(
+ TheEnum::Struct { value: -123 },
+ toml::from_str("{ Struct = { value = -123 } }").unwrap()
+ );
+ assert_eq!(
+ Val {
+ val: TheEnum::Struct { value: -123 }
+ },
+ toml::from_str("val = { Struct = { value = -123 } }").unwrap()
+ );
+ }
+
+ #[test]
+ fn from_dotted_table() {
+ assert_eq!(
+ TheEnum::Struct { value: -123 },
+ toml::from_str(
+ r#"[Struct]
+ value = -123
+ "#
+ )
+ .unwrap()
+ );
+ }
+}
+
+mod enum_array {
+ use super::*;
+
+ #[test]
+ fn from_inline_tables() {
+ let toml_str = r#"
+ enums = [
+ { Plain = {} },
+ { Tuple = { 0 = -123, 1 = true } },
+ { NewType = "value" },
+ { Struct = { value = -123 } }
+ ]"#;
+ assert_eq!(
+ Multi {
+ enums: vec![
+ TheEnum::Plain,
+ TheEnum::Tuple(-123, true),
+ TheEnum::NewType("value".to_string()),
+ TheEnum::Struct { value: -123 },
+ ]
+ },
+ toml::from_str(toml_str).unwrap()
+ );
+ }
+}