Repository: toml-rs/toml (rust)

Issue:
`Vec`s containing `Option` types are removed from output
If a vector contains an option type, and any of its entries are `None`, the vector is completely removed from the output. I believe this is because `toml` tries not to output anything for `None` values, but this chokes when trying to insert a gap with nothing in it.

If you create an enum with the same structure as `Option`, `toml` uses a different format that correctly handles "gaps" (shown in the below example); I believe this should be used for vectors of options.

[Example on Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=640b11e3171204502e51f656fd6e53f1)

Pull request description:
fix(serde): Bubble up nested None errors 
Fixes #603

Patch:
diff --git a/.clippy.toml b/.clippy.toml
index ea0d9fbb..090e2bec 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,4 +1,4 @@
-msrv = "1.66.0"  # MSRV
+msrv = "1.65.0"  # MSRV
 warn-on-all-wildcard-imports = true
 allow-expect-in-tests = true
 allow-unwrap-in-tests = true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 30171cb3..4e6d4b1a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -59,7 +59,7 @@ jobs:
     - name: toml (no-default features)
       run: cargo test -p toml --no-default-features
   msrv:
-    name: "Check MSRV: 1.66.0"
+    name: "Check MSRV: 1.65.0"
     runs-on: ubuntu-latest
     steps:
     - name: Checkout repository
@@ -67,7 +67,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: 1.66.0  # MSRV
+        toolchain: 1.65.0  # MSRV
     - uses: Swatinem/rust-cache@v2
     - name: Default features
       run: cargo check --workspace --all-targets
@@ -129,7 +129,7 @@ jobs:
     - name: Install Rust
       uses: dtolnay/rust-toolchain@stable
       with:
-        toolchain: 1.66.0  # MSRV
+        toolchain: 1.65.0  # MSRV
         components: clippy
     - uses: Swatinem/rust-cache@v2
     - name: Install SARIF tools
diff --git a/Cargo.toml b/Cargo.toml
index 73a1f61c..ac0d78ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ homepage = "https://github.com/toml-rs/toml"
 repository = "https://github.com/toml-rs/toml"
 license = "MIT OR Apache-2.0"
 edition = "2021"
-rust-version = "1.66.0"  # MSRV
+rust-version = "1.65.0"  # MSRV
 include = [
   "build.rs",
   "src/**/*",
diff --git a/crates/toml_edit/src/ser/map.rs b/crates/toml_edit/src/ser/map.rs
index d743e3d5..951a5cb9 100644
--- a/crates/toml_edit/src/ser/map.rs
+++ b/crates/toml_edit/src/ser/map.rs
@@ -1,4 +1,4 @@
-use super::{Error, KeySerializer};
+use super::{Error, KeySerializer, ValueSerializer};
 
 #[doc(hidden)]
 pub enum SerializeMap {
@@ -174,7 +174,8 @@ impl serde::ser::SerializeMap for SerializeInlineTable {
     where
         T: serde::ser::Serialize,
     {
-        let res = value.serialize(super::ValueSerializer {});
+        let mut value_serializer = MapValueSerializer::new();
+        let res = value.serialize(&mut value_serializer);
         match res {
             Ok(item) => {
                 let key = self.key.take().unwrap();
@@ -185,7 +186,7 @@ impl serde::ser::SerializeMap for SerializeInlineTable {
                 self.items.insert(key, kv);
             }
             Err(e) => {
-                if e != Error::UnsupportedNone {
+                if !(e == Error::UnsupportedNone && value_serializer.is_none) {
                     return Err(e);
                 }
             }
@@ -210,7 +211,8 @@ impl serde::ser::SerializeStruct for SerializeInlineTable {
     where
         T: serde::ser::Serialize,
     {
-        let res = value.serialize(super::ValueSerializer {});
+        let mut value_serializer = MapValueSerializer::new();
+        let res = value.serialize(&mut value_serializer);
         match res {
             Ok(item) => {
                 let kv = crate::table::TableKeyValue::new(
@@ -220,7 +222,7 @@ impl serde::ser::SerializeStruct for SerializeInlineTable {
                 self.items.insert(crate::InternalString::from(key), kv);
             }
             Err(e) => {
-                if e != Error::UnsupportedNone {
+                if !(e == Error::UnsupportedNone && value_serializer.is_none) {
                     return Err(e);
                 }
             }
@@ -403,3 +405,183 @@ impl serde::ser::Serializer for DatetimeFieldSerializer {
         Err(Error::DateInvalid)
     }
 }
+
+#[derive(Default)]
+struct MapValueSerializer {
+    is_none: bool,
+}
+
+impl MapValueSerializer {
+    fn new() -> Self {
+        Self { is_none: false }
+    }
+}
+
+impl serde::ser::Serializer for &mut MapValueSerializer {
+    type Ok = crate::Value;
+    type Error = Error;
+    type SerializeSeq = super::SerializeValueArray;
+    type SerializeTuple = super::SerializeValueArray;
+    type SerializeTupleStruct = super::SerializeValueArray;
+    type SerializeTupleVariant = super::SerializeValueArray;
+    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> {
+        ValueSerializer::new().serialize_bool(v)
+    }
+
+    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i8(v)
+    }
+
+    fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i16(v)
+    }
+
+    fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i32(v)
+    }
+
+    fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i64(v)
+    }
+
+    fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u8(v)
+    }
+
+    fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u16(v)
+    }
+
+    fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u32(v)
+    }
+
+    fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u64(v)
+    }
+
+    fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_f32(v)
+    }
+
+    fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_f64(v)
+    }
+
+    fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_char(v)
+    }
+
+    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_str(v)
+    }
+
+    fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_bytes(value)
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        self.is_none = true;
+        Err(Error::UnsupportedNone)
+    }
+
+    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        ValueSerializer::new().serialize_some(value)
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_unit()
+    }
+
+    fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_unit_struct(name)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+    ) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_unit_variant(name, variant_index, variant)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        name: &'static str,
+        value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        ValueSerializer::new().serialize_newtype_struct(name, value)
+    }
+
+    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,
+    {
+        ValueSerializer::new().serialize_newtype_variant(name, variant_index, variant, value)
+    }
+
+    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        ValueSerializer::new().serialize_seq(len)
+    }
+
+    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        ValueSerializer::new().serialize_tuple(len)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        ValueSerializer::new().serialize_tuple_struct(name, len)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        ValueSerializer::new().serialize_tuple_variant(name, variant_index, variant, len)
+    }
+
+    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        ValueSerializer::new().serialize_map(len)
+    }
+
+    fn serialize_struct(
+        self,
+        name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        ValueSerializer::new().serialize_struct(name, len)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        ValueSerializer::new().serialize_struct_variant(name, variant_index, variant, len)
+    }
+}


Test patch:
diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs
index 6e927f73..1d2fe4f0 100644
--- a/crates/toml/tests/testsuite/serde.rs
+++ b/crates/toml/tests/testsuite/serde.rs
@@ -1094,3 +1094,24 @@ fn datetime_offset_issue_496() {
     let output = toml.to_string();
     snapbox::assert_eq(original, output);
 }
+
+#[test]
+fn serialize_array_with_none_value() {
+    #[derive(Serialize)]
+    struct Document {
+        values: Vec<Option<usize>>,
+    }
+
+    let input = Document {
+        values: vec![Some(1), Some(2), Some(3)],
+    };
+    let expected = "values = [1, 2, 3]\n";
+    let raw = toml::to_string(&input).unwrap();
+    snapbox::assert_eq(expected, raw);
+
+    let input = Document {
+        values: vec![Some(1), None, Some(3)],
+    };
+    let err = toml::to_string(&input).unwrap_err();
+    snapbox::assert_eq("unsupported None value", err.to_string());
+}