Repository: toml-rs/toml (language: rust)

Issue:
Structs with optional fields inside vectors of enums result in broken output
This is probably related to #603. I think supporting this use-case is much more reasonable, considering the structs and enums themselves serialize just fine outside of inline tables.

See the second example below for the broken output, and the third for the expected output (which the deserializer accepts)

Example:
[https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c61786f7a9b6c07b122a7947fa52cb72](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c61786f7a9b6c07b122a7947fa52cb72)


Discussion / hints:

Fix patch:
diff --git a/crates/toml/src/fmt.rs b/crates/toml/src/fmt.rs
index 0e96bf05..281cb59c 100644
--- a/crates/toml/src/fmt.rs
+++ b/crates/toml/src/fmt.rs
@@ -1,6 +1,7 @@
 #[derive(Copy, Clone, Default)]
 pub(crate) struct DocumentFormatter {
     pub(crate) multiline_array: bool,
+    is_value: bool,
 }
 
 impl toml_edit::visit_mut::VisitMut for DocumentFormatter {
@@ -9,21 +10,26 @@ impl toml_edit::visit_mut::VisitMut for DocumentFormatter {
     }
 
     fn visit_item_mut(&mut self, node: &mut toml_edit::Item) {
-        let other = std::mem::take(node);
-        let other = match other.into_table().map(toml_edit::Item::Table) {
-            Ok(i) => i,
-            Err(i) => i,
-        };
-        let other = match other
-            .into_array_of_tables()
-            .map(toml_edit::Item::ArrayOfTables)
-        {
-            Ok(i) => i,
-            Err(i) => i,
-        };
-        *node = other;
+        let is_parent_value = self.is_value;
+        if !is_parent_value {
+            let other = std::mem::take(node);
+            let other = match other.into_table().map(toml_edit::Item::Table) {
+                Ok(i) => i,
+                Err(i) => i,
+            };
+            let other = match other
+                .into_array_of_tables()
+                .map(toml_edit::Item::ArrayOfTables)
+            {
+                Ok(i) => i,
+                Err(i) => i,
+            };
+            self.is_value = other.is_value();
+            *node = other;
+        }
 
         toml_edit::visit_mut::visit_item_mut(self, node);
+        self.is_value = is_parent_value;
     }
 
     fn visit_table_mut(&mut self, node: &mut toml_edit::Table) {


Test patch:
diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs
index 1d2fe4f0..13af51d8 100644
--- a/crates/toml/tests/testsuite/serde.rs
+++ b/crates/toml/tests/testsuite/serde.rs
@@ -1115,3 +1115,108 @@ fn serialize_array_with_none_value() {
     let err = toml::to_string(&input).unwrap_err();
     snapbox::assert_eq("unsupported None value", err.to_string());
 }
+
+#[test]
+fn serialize_array_with_optional_struct_field() {
+    #[derive(Debug, Deserialize, Serialize)]
+    struct Document {
+        values: Vec<OptionalField>,
+    }
+
+    #[derive(Debug, Deserialize, Serialize)]
+    struct OptionalField {
+        x: u8,
+        y: Option<u8>,
+    }
+
+    let input = Document {
+        values: vec![
+            OptionalField { x: 0, y: Some(4) },
+            OptionalField { x: 2, y: Some(5) },
+            OptionalField { x: 3, y: Some(7) },
+        ],
+    };
+    let expected = "\
+[[values]]
+x = 0
+y = 4
+
+[[values]]
+x = 2
+y = 5
+
+[[values]]
+x = 3
+y = 7
+";
+    let raw = toml::to_string(&input).unwrap();
+    snapbox::assert_eq(expected, raw);
+
+    let input = Document {
+        values: vec![
+            OptionalField { x: 0, y: Some(4) },
+            OptionalField { x: 2, y: None },
+            OptionalField { x: 3, y: Some(7) },
+        ],
+    };
+    let expected = "\
+[[values]]
+x = 0
+y = 4
+
+[[values]]
+x = 2
+
+[[values]]
+x = 3
+y = 7
+";
+    let raw = toml::to_string(&input).unwrap();
+    snapbox::assert_eq(expected, raw);
+}
+
+#[test]
+fn serialize_array_with_enum_of_optional_struct_field() {
+    #[derive(Debug, Deserialize, Serialize)]
+    struct Document {
+        values: Vec<Choice>,
+    }
+
+    #[derive(Debug, Deserialize, Serialize)]
+    enum Choice {
+        Optional(OptionalField),
+        Empty,
+    }
+
+    #[derive(Debug, Deserialize, Serialize)]
+    struct OptionalField {
+        x: u8,
+        y: Option<u8>,
+    }
+
+    let input = Document {
+        values: vec![
+            Choice::Optional(OptionalField { x: 0, y: Some(4) }),
+            Choice::Empty,
+            Choice::Optional(OptionalField { x: 2, y: Some(5) }),
+            Choice::Optional(OptionalField { x: 3, y: Some(7) }),
+        ],
+    };
+    let expected = "values = [{ Optional = { x = 0, y = 4 } }, \"Empty\", { Optional = { x = 2, y = 5 } }, { Optional = { x = 3, y = 7 } }]
+";
+    let raw = toml::to_string(&input).unwrap();
+    snapbox::assert_eq(expected, raw);
+
+    let input = Document {
+        values: vec![
+            Choice::Optional(OptionalField { x: 0, y: Some(4) }),
+            Choice::Empty,
+            Choice::Optional(OptionalField { x: 2, y: None }),
+            Choice::Optional(OptionalField { x: 3, y: Some(7) }),
+        ],
+    };
+    let expected = "values = [{ Optional = { x = 0, y = 4 } }, \"Empty\", { Optional = { x = 2 } }, { Optional = { x = 3, y = 7 } }]
+";
+    let raw = toml::to_string(&input).unwrap();
+    snapbox::assert_eq(expected, raw);
+}