Repository: toml-rs/toml (rust)

Issue:
Serialization of Datetime outputs a table instead of actual date
I'm using `toml-rs` and was trying to switch to `toml_edit`, but the result of serialized date was not valid.

### Code
```rust
// main.rs
use toml_edit::{Date, Datetime, easy::to_string};
use serde::Serialize;

#[derive(Serialize)]
struct Struct {
    date: Datetime,
}

fn main() {
    let toml = to_string(&Struct {
        date: Datetime {
            date: Some(Date {
                year: 2022,
                month: 1,
                day: 1,
            }),
            time: None,
            offset: None,
        }
    }).unwrap();
    assert_eq!(toml, "date = 2022-01-01");
}
```

```toml
# Cargo.toml
[package]
name = "reproduce"
version = "0.1.0"
edition = "2021"

[dependencies]
toml_edit = { version = "0.14.4", features = ["easy"] }
serde = "1.0"
```

### Expected
`date = 2022-01-01`

### Got
`date = { "$__toml_private_datetime" = "2022-01-01" }`


Pull request:
fix(ser): Serialize datetimes correctly 
Fixes #333

Patch:
diff --git a/crates/toml_edit/src/ser/map.rs b/crates/toml_edit/src/ser/map.rs
new file mode 100644
index 00000000..d743e3d5
--- /dev/null
+++ b/crates/toml_edit/src/ser/map.rs
@@ -0,0 +1,405 @@
+use super::{Error, KeySerializer};
+
+#[doc(hidden)]
+pub enum SerializeMap {
+    Datetime(SerializeDatetime),
+    Table(SerializeInlineTable),
+}
+
+impl SerializeMap {
+    pub(crate) fn table() -> Self {
+        Self::Table(SerializeInlineTable::new())
+    }
+
+    pub(crate) fn table_with_capacity(len: usize) -> Self {
+        Self::Table(SerializeInlineTable::with_capacity(len))
+    }
+
+    pub(crate) fn datetime() -> Self {
+        Self::Datetime(SerializeDatetime::new())
+    }
+}
+
+impl serde::ser::SerializeMap for SerializeMap {
+    type Ok = crate::Value;
+    type Error = Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, input: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        match self {
+            Self::Datetime(s) => s.serialize_key(input),
+            Self::Table(s) => s.serialize_key(input),
+        }
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        match self {
+            Self::Datetime(s) => s.serialize_value(value),
+            Self::Table(s) => s.serialize_value(value),
+        }
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        match self {
+            Self::Datetime(s) => s.end().map(|items| items.into()),
+            Self::Table(s) => s.end().map(|items| items.into()),
+        }
+    }
+}
+
+impl serde::ser::SerializeStruct for SerializeMap {
+    type Ok = crate::Value;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(
+        &mut self,
+        key: &'static str,
+        value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        match self {
+            Self::Datetime(s) => s.serialize_field(key, value),
+            Self::Table(s) => s.serialize_field(key, value),
+        }
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        match self {
+            Self::Datetime(s) => s.end().map(|items| items.into()),
+            Self::Table(s) => s.end().map(|items| items.into()),
+        }
+    }
+}
+
+#[doc(hidden)]
+pub struct SerializeDatetime {
+    value: Option<crate::Datetime>,
+}
+
+impl SerializeDatetime {
+    pub(crate) fn new() -> Self {
+        Self { value: None }
+    }
+}
+
+impl serde::ser::SerializeMap for SerializeDatetime {
+    type Ok = crate::Datetime;
+    type Error = Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, _input: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        unreachable!("datetimes should only be serialized as structs, not maps")
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, _value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        unreachable!("datetimes should only be serialized as structs, not maps")
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        unreachable!("datetimes should only be serialized as structs, not maps")
+    }
+}
+
+impl serde::ser::SerializeStruct for SerializeDatetime {
+    type Ok = crate::Datetime;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(
+        &mut self,
+        key: &'static str,
+        value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        if key == toml_datetime::__unstable::FIELD {
+            self.value = Some(value.serialize(DatetimeFieldSerializer::default())?);
+        }
+
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        self.value.ok_or(Error::UnsupportedNone)
+    }
+}
+
+#[doc(hidden)]
+pub struct SerializeInlineTable {
+    items: crate::table::KeyValuePairs,
+    key: Option<crate::InternalString>,
+}
+
+impl SerializeInlineTable {
+    pub(crate) fn new() -> Self {
+        Self {
+            items: Default::default(),
+            key: Default::default(),
+        }
+    }
+
+    pub(crate) fn with_capacity(len: usize) -> Self {
+        let mut s = Self::new();
+        s.items.reserve(len);
+        s
+    }
+}
+
+impl serde::ser::SerializeMap for SerializeInlineTable {
+    type Ok = crate::InlineTable;
+    type Error = Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, input: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        self.key = None;
+        self.key = Some(input.serialize(KeySerializer)?);
+        Ok(())
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        let res = value.serialize(super::ValueSerializer {});
+        match res {
+            Ok(item) => {
+                let key = self.key.take().unwrap();
+                let kv = crate::table::TableKeyValue::new(
+                    crate::Key::new(&key),
+                    crate::Item::Value(item),
+                );
+                self.items.insert(key, kv);
+            }
+            Err(e) => {
+                if e != Error::UnsupportedNone {
+                    return Err(e);
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(crate::InlineTable::with_pairs(self.items))
+    }
+}
+
+impl serde::ser::SerializeStruct for SerializeInlineTable {
+    type Ok = crate::InlineTable;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(
+        &mut self,
+        key: &'static str,
+        value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        let res = value.serialize(super::ValueSerializer {});
+        match res {
+            Ok(item) => {
+                let kv = crate::table::TableKeyValue::new(
+                    crate::Key::new(key),
+                    crate::Item::Value(item),
+                );
+                self.items.insert(crate::InternalString::from(key), kv);
+            }
+            Err(e) => {
+                if e != Error::UnsupportedNone {
+                    return Err(e);
+                }
+            }
+        };
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(crate::InlineTable::with_pairs(self.items))
+    }
+}
+
+#[derive(Default)]
+struct DatetimeFieldSerializer {}
+
+impl serde::ser::Serializer for DatetimeFieldSerializer {
+    type Ok = toml_datetime::Datetime;
+    type Error = Error;
+    type SerializeSeq = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeTuple = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeTupleStruct = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeTupleVariant = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeMap = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeStruct = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeStructVariant = serde::ser::Impossible<Self::Ok, Self::Error>;
+
+    fn serialize_bool(self, _value: bool) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i8(self, _value: i8) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i16(self, _value: i16) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i32(self, _value: i32) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i64(self, _value: i64) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u8(self, _value: u8) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u16(self, _value: u16) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u32(self, _value: u32) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u64(self, _value: u64) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_f32(self, _value: f32) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_f64(self, _value: f64) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_char(self, _value: char) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
+        v.parse::<toml_datetime::Datetime>().map_err(Error::custom)
+    }
+
+    fn serialize_bytes(self, _value: &[u8]) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_some<T: ?Sized>(self, _value: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<Self::Ok, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_struct(
+        self,
+        _name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+}
diff --git a/crates/toml_edit/src/ser/mod.rs b/crates/toml_edit/src/ser/mod.rs
index e72b1d18..9d639013 100644
--- a/crates/toml_edit/src/ser/mod.rs
+++ b/crates/toml_edit/src/ser/mod.rs
@@ -4,13 +4,13 @@
 
 mod array;
 mod key;
+mod map;
 mod pretty;
-mod table;
 mod value;
 
 pub(crate) use array::*;
 pub(crate) use key::*;
-pub(crate) use table::*;
+pub(crate) use map::*;
 
 use crate::visit_mut::VisitMut;
 
@@ -24,6 +24,8 @@ pub enum Error {
     UnsupportedNone,
     /// Key was not convertable to `String` for serializing to TOML
     KeyNotString,
+    /// A serialized date was invalid
+    DateInvalid,
     /// Other serialization error
     Custom(String),
 }
@@ -53,6 +55,7 @@ impl std::fmt::Display for Error {
             Self::UnsupportedType(None) => write!(formatter, "unsupported rust type"),
             Self::UnsupportedNone => "unsupported None value".fmt(formatter),
             Self::KeyNotString => "map key was not a string".fmt(formatter),
+            Self::DateInvalid => "a serialized date was invalid".fmt(formatter),
             Self::Custom(s) => s.fmt(formatter),
         }
     }
diff --git a/crates/toml_edit/src/ser/table.rs b/crates/toml_edit/src/ser/table.rs
deleted file mode 100644
index 81285bc4..00000000
--- a/crates/toml_edit/src/ser/table.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-use super::{Error, KeySerializer};
-
-#[doc(hidden)]
-pub struct SerializeValueTable {
-    inner: SerializeKeyValuePairs,
-}
-
-impl SerializeValueTable {
-    pub(crate) fn new() -> Self {
-        Self {
-            inner: SerializeKeyValuePairs::new(),
-        }
-    }
-
-    pub(crate) fn with_capacity(len: usize) -> Self {
-        Self {
-            inner: SerializeKeyValuePairs::with_capacity(len),
-        }
-    }
-}
-
-impl serde::ser::SerializeMap for SerializeValueTable {
-    type Ok = crate::Value;
-    type Error = Error;
-
-    fn serialize_key<T: ?Sized>(&mut self, input: &T) -> Result<(), Self::Error>
-    where
-        T: serde::ser::Serialize,
-    {
-        self.inner.serialize_key(input)
-    }
-
-    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
-    where
-        T: serde::ser::Serialize,
-    {
-        self.inner.serialize_value(value)
-    }
-
-    fn end(self) -> Result<Self::Ok, Self::Error> {
-        self.inner
-            .end()
-            .map(|items| crate::Value::InlineTable(crate::InlineTable::with_pairs(items)))
-    }
-}
-
-impl serde::ser::SerializeStruct for SerializeValueTable {
-    type Ok = crate::Value;
-    type Error = Error;
-
-    fn serialize_field<T: ?Sized>(
-        &mut self,
-        key: &'static str,
-        value: &T,
-    ) -> Result<(), Self::Error>
-    where
-        T: serde::ser::Serialize,
-    {
-        self.inner.serialize_field(key, value)
-    }
-
-    fn end(self) -> Result<Self::Ok, Self::Error> {
-        self.inner
-            .end()
-            .map(|items| crate::Value::InlineTable(crate::InlineTable::with_pairs(items)))
-    }
-}
-
-struct SerializeKeyValuePairs {
-    items: crate::table::KeyValuePairs,
-    key: Option<crate::InternalString>,
-}
-
-impl SerializeKeyValuePairs {
-    pub(crate) fn new() -> Self {
-        Self {
-            items: Default::default(),
-            key: Default::default(),
-        }
-    }
-
-    pub(crate) fn with_capacity(len: usize) -> Self {
-        let mut s = Self::new();
-        s.items.reserve(len);
-        s
-    }
-}
-
-impl serde::ser::SerializeMap for SerializeKeyValuePairs {
-    type Ok = crate::table::KeyValuePairs;
-    type Error = Error;
-
-    fn serialize_key<T: ?Sized>(&mut self, input: &T) -> Result<(), Self::Error>
-    where
-        T: serde::ser::Serialize,
-    {
-        self.key = None;
-        self.key = Some(input.serialize(KeySerializer)?);
-        Ok(())
-    }
-
-    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
-    where
-        T: serde::ser::Serialize,
-    {
-        let res = value.serialize(super::ValueSerializer {});
-        match res {
-            Ok(item) => {
-                let key = self.key.take().unwrap();
-                let kv = crate::table::TableKeyValue::new(
-                    crate::Key::new(&key),
-                    crate::Item::Value(item),
-                );
-                self.items.insert(key, kv);
-            }
-            Err(e) => {
-                if e != Error::UnsupportedNone {
-                    return Err(e);
-                }
-            }
-        }
-        Ok(())
-    }
-
-    fn end(self) -> Result<Self::Ok, Self::Error> {
-        Ok(self.items)
-    }
-}
-
-impl serde::ser::SerializeStruct for SerializeKeyValuePairs {
-    type Ok = crate::table::KeyValuePairs;
-    type Error = Error;
-
-    fn serialize_field<T: ?Sized>(
-        &mut self,
-        key: &'static str,
-        value: &T,
-    ) -> Result<(), Self::Error>
-    where
-        T: serde::ser::Serialize,
-    {
-        let res = value.serialize(super::ValueSerializer {});
-        match res {
-            Ok(item) => {
-                let kv = crate::table::TableKeyValue::new(
-                    crate::Key::new(key),
-                    crate::Item::Value(item),
-                );
-                self.items.insert(crate::InternalString::from(key), kv);
-            }
-            Err(e) => {
-                if e != Error::UnsupportedNone {
-                    return Err(e);
-                }
-            }
-        };
-        Ok(())
-    }
-
-    fn end(self) -> Result<Self::Ok, Self::Error> {
-        Ok(self.items)
-    }
-}
diff --git a/crates/toml_edit/src/ser/value.rs b/crates/toml_edit/src/ser/value.rs
index 089c93c6..39598965 100644
--- a/crates/toml_edit/src/ser/value.rs
+++ b/crates/toml_edit/src/ser/value.rs
@@ -27,8 +27,8 @@ impl serde::ser::Serializer for ValueSerializer {
     type SerializeTuple = super::SerializeValueArray;
     type SerializeTupleStruct = super::SerializeValueArray;
     type SerializeTupleVariant = super::SerializeValueArray;
-    type SerializeMap = super::SerializeValueTable;
-    type SerializeStruct = super::SerializeValueTable;
+    type SerializeMap = super::SerializeMap;
+    type SerializeStruct = super::SerializeMap;
     type SerializeStructVariant = serde::ser::Impossible<Self::Ok, Self::Error>;
 
     fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
@@ -173,18 +173,22 @@ impl serde::ser::Serializer for ValueSerializer {
 
     fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
         let serializer = match len {
-            Some(len) => super::SerializeValueTable::with_capacity(len),
-            None => super::SerializeValueTable::new(),
+            Some(len) => super::SerializeMap::table_with_capacity(len),
+            None => super::SerializeMap::table(),
         };
         Ok(serializer)
     }
 
     fn serialize_struct(
         self,
-        _name: &'static str,
+        name: &'static str,
         len: usize,
     ) -> Result<Self::SerializeStruct, Self::Error> {
-        self.serialize_map(Some(len))
+        if name == toml_datetime::__unstable::NAME {
+            Ok(super::SerializeMap::datetime())
+        } else {
+            self.serialize_map(Some(len))
+        }
     }
 
     fn serialize_struct_variant(


Test patch:
diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs
index f88dd106..e7db4fb8 100644
--- a/crates/toml/tests/testsuite/serde.rs
+++ b/crates/toml/tests/testsuite/serde.rs
@@ -1026,3 +1026,27 @@ fn table_type_enum_regression_issue_388() {
         "#;
     assert!(toml::from_str::<DataFile>(inline_table).is_ok());
 }
+
+#[test]
+fn serialize_datetime_issue_333() {
+    use toml::{to_string, value::Date, value::Datetime};
+
+    #[derive(Serialize)]
+    struct Struct {
+        date: Datetime,
+    }
+
+    let toml = to_string(&Struct {
+        date: Datetime {
+            date: Some(Date {
+                year: 2022,
+                month: 1,
+                day: 1,
+            }),
+            time: None,
+            offset: None,
+        },
+    })
+    .unwrap();
+    assert_eq!(toml, "date = 2022-01-01\n");
+}