Repository: alexcrichton/toml-rs
Pull request #329 (2019-09-04 16:13:14)

Nested dotted-table enums
Add support for deserializing enums from nested dotted tables.


Patch:
diff --git a/src/de.rs b/src/de.rs
index f9a3a4c..03372c6 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -8,6 +8,7 @@ use std::borrow::Cow;
 use std::error;
 use std::f64;
 use std::fmt;
+use std::marker::PhantomData;
 use std::mem::discriminant;
 use std::str;
 use std::vec;
@@ -81,12 +82,12 @@ where
 }
 
 /// Errors that can occur when deserializing a type.
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 pub struct Error {
     inner: Box<ErrorInner>,
 }
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 struct ErrorInner {
     kind: ErrorKind,
     line: Option<usize>,
@@ -97,7 +98,7 @@ struct ErrorInner {
 }
 
 /// Errors that can occur when deserializing a type.
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone)]
 enum ErrorKind {
     /// EOF was reached when looking for a value
     UnexpectedEof,
@@ -285,9 +286,34 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
         }
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if name == spanned::NAME && fields == [spanned::START, spanned::END, spanned::VALUE] {
+            let start = 0;
+            let end = self.input.len();
+
+            let res = visitor.visit_map(SpannedDeserializer {
+                phantom_data: PhantomData,
+                start: Some(start),
+                value: Some(self),
+                end: Some(end),
+            });
+            return res;
+        }
+
+        self.deserialize_any(visitor)
+    }
+
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
-        bytes byte_buf map struct unit newtype_struct
+        bytes byte_buf map unit newtype_struct
         ignored_any unit_struct tuple_struct tuple option identifier
     }
 }
@@ -515,10 +541,41 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> {
         visitor.visit_newtype_struct(self)
     }
 
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if self.tables.len() != 1 {
+            return Err(Error::custom(
+                Some(self.cur),
+                "enum table must contain exactly one table".into(),
+            ));
+        }
+        let table = &mut self.tables[0];
+        let values = table.values.take().expect("table has no values?");
+        if table.header.len() == 0 {
+            return Err(self.de.error(self.cur, ErrorKind::EmptyTableKey));
+        }
+        let name = table.header[table.header.len() - 1].to_owned();
+        visitor.visit_enum(DottedTableDeserializer {
+            name: name,
+            value: Value {
+                e: E::DottedTable(values),
+                start: 0,
+                end: 0,
+            },
+        })
+    }
+
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
         bytes byte_buf map struct unit identifier
-        ignored_any unit_struct tuple_struct tuple enum
+        ignored_any unit_struct tuple_struct tuple
     }
 }
 
@@ -664,6 +721,7 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
             let end = self.value.end;
 
             return visitor.visit_map(SpannedDeserializer {
+                phantom_data: PhantomData,
                 start: Some(start),
                 value: Some(self.value),
                 end: Some(end),
@@ -741,6 +799,14 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
     }
 }
 
+impl<'de, 'b> de::IntoDeserializer<'de, Error> for &'b mut Deserializer<'de> {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
 impl<'de> de::IntoDeserializer<'de, Error> for Value<'de> {
     type Deserializer = ValueDeserializer<'de>;
 
@@ -749,13 +815,17 @@ impl<'de> de::IntoDeserializer<'de, Error> for Value<'de> {
     }
 }
 
-struct SpannedDeserializer<'a> {
+struct SpannedDeserializer<'de, T: de::IntoDeserializer<'de, Error>> {
+    phantom_data: PhantomData<&'de ()>,
     start: Option<usize>,
     end: Option<usize>,
-    value: Option<Value<'a>>,
+    value: Option<T>,
 }
 
-impl<'de> de::MapAccess<'de> for SpannedDeserializer<'de> {
+impl<'de, T> de::MapAccess<'de> for SpannedDeserializer<'de, T>
+where
+    T: de::IntoDeserializer<'de, Error>,
+{
     type Error = Error;
 
     fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>


Test patch:
diff --git a/test-suite/tests/spanned.rs b/test-suite/tests/spanned.rs
index 5f6d5a2..5130a72 100644
--- a/test-suite/tests/spanned.rs
+++ b/test-suite/tests/spanned.rs
@@ -4,6 +4,7 @@ extern crate toml;
 extern crate serde_derive;
 
 use std::collections::HashMap;
+use std::fmt::Debug;
 use toml::value::Datetime;
 use toml::Spanned;
 
@@ -31,33 +32,56 @@ fn test_spanned_field() {
         foo: Spanned<T>,
     }
 
-    fn good<'de, T>(s: &'de str, expected: &str)
+    #[derive(Deserialize)]
+    struct BareFoo<T> {
+        foo: T,
+    }
+
+    fn good<'de, T>(s: &'de str, expected: &str, end: Option<usize>)
     where
-        T: serde::Deserialize<'de>,
+        T: serde::Deserialize<'de> + Debug + PartialEq,
     {
         let foo: Foo<T> = toml::from_str(s).unwrap();
 
         assert_eq!(6, foo.foo.start());
-        assert_eq!(s.len(), foo.foo.end());
+        if let Some(end) = end {
+            assert_eq!(end, foo.foo.end());
+        } else {
+            assert_eq!(s.len(), foo.foo.end());
+        }
         assert_eq!(expected, &s[foo.foo.start()..foo.foo.end()]);
+
+        // Test for Spanned<> at the top level
+        let foo_outer: Spanned<BareFoo<T>> = toml::from_str(s).unwrap();
+
+        assert_eq!(0, foo_outer.start());
+        assert_eq!(s.len(), foo_outer.end());
+        assert_eq!(foo.foo.into_inner(), foo_outer.into_inner().foo);
     }
 
-    good::<String>("foo = \"foo\"", "\"foo\"");
-    good::<u32>("foo = 42", "42");
+    good::<String>("foo = \"foo\"", "\"foo\"", None);
+    good::<u32>("foo = 42", "42", None);
     // leading plus
-    good::<u32>("foo = +42", "+42");
+    good::<u32>("foo = +42", "+42", None);
     // table
     good::<HashMap<String, u32>>(
         "foo = {\"foo\" = 42, \"bar\" = 42}",
         "{\"foo\" = 42, \"bar\" = 42}",
+        None,
     );
     // array
-    good::<Vec<u32>>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]");
+    good::<Vec<u32>>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None);
     // datetime
-    good::<String>("foo = \"1997-09-09T09:09:09Z\"", "\"1997-09-09T09:09:09Z\"");
+    good::<String>(
+        "foo = \"1997-09-09T09:09:09Z\"",
+        "\"1997-09-09T09:09:09Z\"",
+        None,
+    );
 
     for expected in good_datetimes() {
         let s = format!("foo = {}", expected);
-        good::<Datetime>(&s, expected);
+        good::<Datetime>(&s, expected, None);
     }
+    // ending at something other than the absolute end
+    good::<u32>("foo = 42\nnoise = true", "42", Some(8));
 }
diff --git a/tests/enum_external_deserialize.rs b/tests/enum_external_deserialize.rs
index 3c38580..6d45b48 100644
--- a/tests/enum_external_deserialize.rs
+++ b/tests/enum_external_deserialize.rs
@@ -2,6 +2,11 @@
 extern crate serde_derive;
 extern crate toml;
 
+#[derive(Debug, Deserialize, PartialEq)]
+struct OuterStruct {
+    inner: TheEnum,
+}
+
 #[derive(Debug, Deserialize, PartialEq)]
 enum TheEnum {
     Plain,
@@ -181,6 +186,21 @@ mod enum_struct {
             .unwrap()
         );
     }
+
+    #[test]
+    fn from_nested_dotted_table() {
+        assert_eq!(
+            OuterStruct {
+                inner: TheEnum::Struct { value: -123 }
+            },
+            toml::from_str(
+                r#"[inner.Struct]
+                value = -123
+                "#
+            )
+            .unwrap()
+        );
+    }
 }
 
 mod enum_array {