Add configurable boolean defaults for bare node names
Users can now specify custom default values for bare KDL node names
(without arguments) using serde's deserialize_with attribute:

    #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
    enabled: bool,
    
    #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]  
    disabled: bool,

KDL input:
    enabled          // → true (bare_true default)
    disabled         // → false (bare_false default) 
    enabled #false   // → false (explicit override)

Includes comprehensive tests and example demonstrating the feature.
change tqymtrxwqplxpwsronopzpsvppusrmno
commit 1bb49825f98fbb1a26a8ec7fc92c42a6daa1aac0
author Alpha Chen <alpha@kejadlen.dev>
date
parent qpkpzpmm
diff --git a/examples/boolean_defaults.rs b/examples/boolean_defaults.rs
new file mode 100644
index 0000000..ca353d1
--- /dev/null
+++ b/examples/boolean_defaults.rs
@@ -0,0 +1,47 @@
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+struct Config {
+    #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+    enabled: bool,
+    
+    #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+    debug: bool,
+    
+    // Regular boolean field (requires explicit value)
+    production: bool,
+}
+
+fn main() {
+    let kdl_input = r#"
+        enabled          // defaults to true
+        debug            // defaults to false  
+        production #true // explicit value required
+    "#;
+    
+    let config: Config = serde_kdl2::from_str(kdl_input).unwrap();
+    
+    println!("{:#?}", config);
+    // Output:
+    // Config {
+    //     enabled: true,     // from bare_true default
+    //     debug: false,      // from bare_false default
+    //     production: true,  // from explicit #true
+    // }
+    
+    // You can still override the defaults with explicit values
+    let kdl_override = r#"
+        enabled #false   // overrides bare_true default
+        debug #true      // overrides bare_false default
+        production #false
+    "#;
+    
+    let config2: Config = serde_kdl2::from_str(kdl_override).unwrap();
+    println!("{:#?}", config2);
+    // Output:
+    // Config {
+    //     enabled: false,    // explicit override
+    //     debug: true,       // explicit override
+    //     production: false,
+    // }
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index f44afca..b92eb56 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,6 +49,31 @@
 //! enabled #true
 //! ```
 //!
+//! ### Boolean Shorthand
+//!
+//! Boolean fields can use custom defaults for bare node names (without arguments)
+//! using the `deserialize_with` attribute:
+//!
+//! ```rust
+//! use serde::Deserialize;
+//!
+//! #[derive(Deserialize)]
+//! struct Config {
+//!     #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+//!     enabled: bool,
+//!     
+//!     #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+//!     disabled: bool,
+//! }
+//! ```
+//!
+//! ```kdl
+//! enabled          // → true (due to bare_true)
+//! disabled         // → false (due to bare_false)
+//! enabled #false   // → false (explicit value overrides default)
+//! disabled #true   // → true (explicit value overrides default)
+//! ```
+//!
 //! ### Nested Structs
 //!
 //! Nested structs use children blocks:
@@ -121,3 +146,75 @@ pub mod ser;
 pub use de::{from_doc, from_str};
 pub use error::Error;
 pub use ser::{to_doc, to_string, to_string_pretty};
+
+/// Serde helper functions for custom boolean defaults with bare node names.
+pub mod bool_defaults {
+    use serde::{de, Deserializer};
+
+    /// Deserializes a boolean field where bare node names default to `true`.
+    /// 
+    /// Use with `#[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]`.
+    /// 
+    /// # Examples
+    /// 
+    /// ```kdl
+    /// enabled        // → true
+    /// enabled #true  // → true
+    /// enabled #false // → false
+    /// ```
+    pub fn bare_true<'de, D>(deserializer: D) -> Result<bool, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        bare_default::<D, true>(deserializer)
+    }
+
+    /// Deserializes a boolean field where bare node names default to `false`.
+    /// 
+    /// Use with `#[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]`.
+    /// 
+    /// # Examples
+    /// 
+    /// ```kdl
+    /// disabled        // → false
+    /// disabled #true  // → true
+    /// disabled #false // → false
+    /// ```
+    pub fn bare_false<'de, D>(deserializer: D) -> Result<bool, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        bare_default::<D, false>(deserializer)
+    }
+
+    fn bare_default<'de, D, const DEFAULT: bool>(deserializer: D) -> Result<bool, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct BareDefaultVisitor<const DEFAULT: bool>;
+
+        impl<'de, const DEFAULT: bool> de::Visitor<'de> for BareDefaultVisitor<DEFAULT> {
+            type Value = bool;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                write!(formatter, "a boolean value or bare node name")
+            }
+
+            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(value)
+            }
+
+            fn visit_unit<E>(self) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(DEFAULT)
+            }
+        }
+
+        deserializer.deserialize_any(BareDefaultVisitor::<DEFAULT>)
+    }
+}
diff --git a/tests/integration_primitives.rs b/tests/integration_primitives.rs
index b83e081..434faaa 100644
--- a/tests/integration_primitives.rs
+++ b/tests/integration_primitives.rs
@@ -117,6 +117,95 @@ roundtrip!(
     }
 );
 
+// ── Boolean Defaults ───────────────────────────────────────────────────
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct BooleanDefaults {
+    #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+    enabled: bool,
+    
+    #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+    disabled: bool,
+}
+
+// Test that bare node names use the configured defaults
+deser_ok!(
+    boolean_defaults_bare,
+    BooleanDefaults,
+    indoc! {"
+        enabled
+        disabled
+    "},
+    BooleanDefaults {
+        enabled: true,   // bare_true default
+        disabled: false, // bare_false default
+    }
+);
+
+// Test that explicit values override defaults
+deser_ok!(
+    boolean_defaults_explicit_override,
+    BooleanDefaults,
+    indoc! {"
+        enabled #false
+        disabled #true
+    "},
+    BooleanDefaults {
+        enabled: false,  // overrides bare_true default
+        disabled: true,  // overrides bare_false default
+    }
+);
+
+// Test mixed bare and explicit values
+deser_ok!(
+    boolean_defaults_mixed,
+    BooleanDefaults,
+    indoc! {"
+        enabled
+        disabled #true
+    "},
+    BooleanDefaults {
+        enabled: true,  // bare_true default
+        disabled: true, // explicit value
+    }
+);
+
+// Test single field with bare_true default
+#[test]
+fn boolean_defaults_single_bare_true() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    struct W {
+        #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+        flag: bool,
+    }
+    let val: W = serde_kdl2::from_str("flag").unwrap();
+    assert_eq!(val.flag, true);
+}
+
+// Test single field with bare_false default
+#[test]
+fn boolean_defaults_single_bare_false() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    struct W {
+        #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+        flag: bool,
+    }
+    let val: W = serde_kdl2::from_str("flag").unwrap();
+    assert_eq!(val.flag, false);
+}
+
+// Test that explicit false works with bare_true default
+#[test]
+fn boolean_defaults_explicit_false_with_bare_true() {
+    #[derive(Debug, Deserialize, PartialEq)]
+    struct W {
+        #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+        flag: bool,
+    }
+    let val: W = serde_kdl2::from_str("flag #false").unwrap();
+    assert_eq!(val.flag, false);
+}
+
 // ── Characters ──────────────────────────────────────────────────────────
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]