aboutsummaryrefslogtreecommitdiff
path: root/src/de.rs
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2018-11-28 09:50:38 -0600
committerGitHub <noreply@github.com>2018-11-28 09:50:38 -0600
commitf667495ae34395be9fc3e4bb8801cacf52c2253e (patch)
treef1b9e15634802581529dce49b93d8e87fce5179b /src/de.rs
parent7d93f00ea02acbb9f45c68f9d4bcf1f04f392e46 (diff)
parentdf7ec3a4b66433118f09b12d5f32a10368a57852 (diff)
downloadmilf-rs-f667495ae34395be9fc3e4bb8801cacf52c2253e.tar.gz
milf-rs-f667495ae34395be9fc3e4bb8801cacf52c2253e.zip
Merge pull request #264 from azriel91/feature/225/externally-tagged-enums
Issue 255: Externally tagged enum deserialization
Diffstat (limited to 'src/de.rs')
-rw-r--r--src/de.rs448
1 files changed, 391 insertions, 57 deletions
diff --git a/src/de.rs b/src/de.rs
index b5633ea..9334cd3 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -159,12 +159,35 @@ 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),
+
+ /// 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,
/// 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<String>,
+ /// Keys that may be specified.
+ available: &'static [&'static str],
+ },
+
#[doc(hidden)]
__Nonexhaustive,
}
@@ -182,47 +205,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(),
@@ -237,6 +221,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,
@@ -245,15 +230,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(),
+ })),
}
}
@@ -497,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> {
@@ -553,6 +564,31 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
}
}
+ if self.validate_struct_keys {
+ match &self.value.e {
+ &E::InlineTable(ref values) | &E::DottedTable(ref values) => {
+ let extra_fields = values.iter()
+ .filter_map(|key_value| {
+ let (ref key, ref _val) = *key_value;
+ if !fields.contains(&&(**key)) {
+ Some(key.clone())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<Cow<'de, str>>>();
+
+ if !extra_fields.is_empty() {
+ return Err(Error::from_kind(ErrorKind::UnexpectedKeys {
+ keys: extra_fields.iter().map(|k| k.to_string()).collect::<Vec<_>>(),
+ available: fields,
+ }));
+ }
+ }
+ _ => {}
+ }
+ }
+
if name == spanned::NAME && fields == &[spanned::START, spanned::END, spanned::VALUE] {
let start = self.value.start;
let end = self.value.end;
@@ -585,7 +621,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) => {
+ 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 inline table",
+ found: e.type_name(),
+ })),
}
}
@@ -697,6 +753,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>>,
@@ -724,6 +799,124 @@ impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> {
}
}
+impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> {
+ type Error = Error;
+ type Variant = TableEnumDeserializer<'de>;
+
+ fn variant_seed<V>(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 table",
+ }))
+ }
+ };
+
+ seed.deserialize(StrDeserializer::new(key))
+ .map(|val| (val, TableEnumDeserializer { value: value }))
+ }
+}
+
+/// 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.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<T>(self, seed: T) -> Result<T::Value, Self::Error>
+ where
+ T: de::DeserializeSeed<'de>,
+ {
+ 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>,
+ {
+ match self.value.e {
+ E::InlineTable(values) | E::DottedTable(values) => {
+ let tuple_values = values
+ .into_iter()
+ .enumerate()
+ .map(|(index, (key, value))| match key.parse::<usize>() {
+ Ok(key_index) if key_index == index => Ok(value),
+ Ok(_) | Err(_) => Err(Error::from_kind(ErrorKind::ExpectedTupleIndex {
+ expected: index,
+ found: key.to_string(),
+ })),
+ })
+ // Fold all values into a `Vec`, or return the first error.
+ .fold(Ok(Vec::with_capacity(len)), |result, value_result| {
+ result.and_then(move |mut tuple_values| match value_result {
+ Ok(value) => {
+ tuple_values.push(value);
+ Ok(tuple_values)
+ }
+ // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>`
+ Err(e) => Err(e),
+ })
+ })?;
+
+ if tuple_values.len() == len {
+ de::Deserializer::deserialize_seq(
+ ValueDeserializer::new(Value {
+ e: E::Array(tuple_values),
+ start: self.value.start,
+ end: self.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<V>(
+ self,
+ fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Self::Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ de::Deserializer::deserialize_struct(
+ ValueDeserializer::new(self.value).with_struct_key_validation(),
+ "", // TODO: this should be the variant name
+ fields,
+ visitor,
+ )
+ }
+}
impl<'a> Deserializer<'a> {
/// Creates a new deserializer which will be deserializing the string
@@ -754,6 +947,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()?;
@@ -876,7 +1116,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)
@@ -1170,6 +1459,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>>,
@@ -1191,12 +1492,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)?;
@@ -1384,8 +1685,23 @@ 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::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?,
+ 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)?
+ }
+ ErrorKind::UnexpectedKeys { ref keys, available } => {
+ write!(
+ f,
+ "unexpected keys in table: `{:?}`, available keys: `{:?}`",
+ keys,
+ available
+ )?
+ }
ErrorKind::__Nonexhaustive => panic!(),
}
@@ -1429,8 +1745,11 @@ 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",
ErrorKind::DottedKeyInvalidType => "dotted key invalid type",
+ ErrorKind::UnexpectedKeys { .. } => "unexpected keys in table",
ErrorKind::__Nonexhaustive => panic!(),
}
}
@@ -1509,6 +1828,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) {