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.
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)]