Reject duplicate nodes when deserializing scalar fields
Add only_node() helper that errors when multiple nodes share the same
name but the target type is a scalar (not Vec). Previously the first
occurrence was silently used and duplicates were dropped. Vec fields
continue to work via deserialize_seq which uses all matching nodes.
change tulwqkzvxonvvnzwlmlyrypzlrsxoynm
commit 2e458b4eff052446105a710ba4fa222616f7f4b8
author Alpha Chen <alpha@kejadlen.dev>
date
parent rzrsynqk
diff --git a/src/de.rs b/src/de.rs
index 6ebdfb3..00d0904 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -173,8 +173,21 @@ impl<'a> FieldDeserializer<'a> {
             .ok_or_else(|| Error::Message("expected at least one node".into()))
     }
 
+    /// Returns the single node, or errors if there are duplicates.
+    /// Used by scalar deserialize methods to reject ambiguous duplicate nodes.
+    fn only_node(&self) -> Result<&'a KdlNode> {
+        if self.nodes.len() > 1 {
+            return Err(Error::Message(format!(
+                "duplicate node '{}': expected a single node for scalar field, found {}",
+                self.nodes[0].name().value(),
+                self.nodes.len()
+            )));
+        }
+        self.first_node()
+    }
+
     fn first_arg(&self) -> Result<&'a KdlValue> {
-        let node = self.first_node()?;
+        let node = self.only_node()?;
         first_arg_of(node)
     }
 }
@@ -395,7 +408,7 @@ impl<'de, 'a> de::Deserializer<'de> for FieldDeserializer<'a> {
     }
 
     fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
-        let node = self.first_node()?;
+        let node = self.only_node()?;
         // If node has children, deserialize from children document
         if let Some(children) = node.children() {
             return visitor.visit_map(DocumentMapAccess::new(children));
@@ -418,7 +431,7 @@ impl<'de, 'a> de::Deserializer<'de> for FieldDeserializer<'a> {
         fields: &'static [&'static str],
         visitor: V,
     ) -> Result<V::Value> {
-        let node = self.first_node()?;
+        let node = self.only_node()?;
 
         // If node has children, deserialize struct from children
         if let Some(children) = node.children() {
@@ -455,7 +468,7 @@ impl<'de, 'a> de::Deserializer<'de> for FieldDeserializer<'a> {
         _variants: &'static [&'static str],
         visitor: V,
     ) -> Result<V::Value> {
-        let node = self.first_node()?;
+        let node = self.only_node()?;
         let args = node_args(node);
 
         // Unit variant: first argument is variant name string
diff --git a/tests/integration.rs b/tests/integration.rs
index fdd4422..9669e6b 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -1050,6 +1050,14 @@ fn serde_error_custom_impls() {
 // ════════════════════════════════════════════════════════════════════════
 
 deser_err!(deserialize_invalid_kdl, SimpleConfig, "{{{{invalid");
+deser_field_err!(
+    deserialize_duplicate_scalar_node,
+    name: String,
+    indoc! {r#"
+        name "first"
+        name "second"
+    "#}
+);
 deser_field_err!(deserialize_bool_type_mismatch, flag: bool, r#"flag "not a bool""#);
 deser_field_err!(deserialize_string_type_mismatch, name: String, "name 42");
 deser_field_err!(deserialize_int_type_mismatch, value: i32, r#"value "not a number""#);