Make bare node defaults extensible to any type
Refactored the boolean defaults implementation to provide a clean pattern
that users can follow to create custom defaults for any type:
- Moved boolean helpers to module
- Removed backward compatibility (bool_defaults module)
- Updated documentation with cleaner examples
- Added comprehensive example showing extension to i32 and String types
- Maintained all existing functionality and tests
Users can now create custom deserializers for any type by implementing
the visitor pattern, while boolean defaults remain convenient to use:
#[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_true")]
enabled: bool,
KDL: enabled // → true
KDL: enabled #false // → false (explicit override)
diff --git a/examples/boolean_defaults.rs b/examples/boolean_defaults.rs
index ca353d1..a34522d 100644
--- a/examples/boolean_defaults.rs
+++ b/examples/boolean_defaults.rs
@@ -1,22 +1,109 @@
-use serde::Deserialize;
+use serde::de::{self, Visitor};
+use serde::{Deserialize, Deserializer};
+
+// Custom deserializer for integer with default value
+fn retry_count_default<'de, D>(deserializer: D) -> Result<i32, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ struct RetryCountDefaultVisitor;
+
+ impl<'de> Visitor<'de> for RetryCountDefaultVisitor {
+ type Value = i32;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(formatter, "an integer or bare node name")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(value as i32)
+ }
+
+ fn visit_i128<E>(self, value: i128) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(value as i32)
+ }
+
+ fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(value as i32)
+ }
+
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(3) // Default retry count for bare nodes
+ }
+ }
+
+ deserializer.deserialize_any(RetryCountDefaultVisitor)
+}
+
+// Custom deserializer for string with default value
+fn name_default<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ struct NameDefaultVisitor;
+
+ impl<'de> Visitor<'de> for NameDefaultVisitor {
+ type Value = String;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(formatter, "a string or bare node name")
+ }
+
+ fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok(value.to_string())
+ }
+
+ fn visit_unit<E>(self) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ Ok("unknown".to_string()) // Default name for bare nodes
+ }
+ }
+
+ deserializer.deserialize_any(NameDefaultVisitor)
+}
#[derive(Debug, Deserialize)]
struct Config {
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_true")]
enabled: bool,
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_false")]
debug: bool,
- // Regular boolean field (requires explicit value)
- production: bool,
+ #[serde(deserialize_with = "retry_count_default")]
+ retry_count: i32,
+
+ #[serde(deserialize_with = "name_default")]
+ username: String,
+
+ // Regular field (requires explicit value)
+ port: u16,
}
fn main() {
let kdl_input = r#"
enabled // defaults to true
debug // defaults to false
- production #true // explicit value required
+ retry_count // defaults to 3
+ username // defaults to "unknown"
+ port 8080 // explicit value required
"#;
let config: Config = serde_kdl2::from_str(kdl_input).unwrap();
@@ -24,24 +111,30 @@ fn main() {
println!("{:#?}", config);
// Output:
// Config {
- // enabled: true, // from bare_true default
- // debug: false, // from bare_false default
- // production: true, // from explicit #true
+ // enabled: true, // from bare_true default
+ // debug: false, // from bare_false default
+ // retry_count: 3, // from custom default
+ // username: "unknown", // from custom default
+ // port: 8080, // from explicit value
// }
// 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
+ enabled #false // overrides bare_true default
+ debug #true // overrides bare_false default
+ retry_count 10 // overrides default
+ username "alice" // overrides default
+ port 3000
"#;
let config2: Config = serde_kdl2::from_str(kdl_override).unwrap();
println!("{:#?}", config2);
// Output:
// Config {
- // enabled: false, // explicit override
- // debug: true, // explicit override
- // production: false,
+ // enabled: false, // explicit override
+ // debug: true, // explicit override
+ // retry_count: 10, // explicit override
+ // username: "alice", // explicit override
+ // port: 3000,
// }
}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index b92eb56..ee8bbfe 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,31 +49,34 @@
//! enabled #true
//! ```
//!
-//! ### Boolean Shorthand
+//! ### Bare Node Defaults
//!
-//! Boolean fields can use custom defaults for bare node names (without arguments)
-//! using the `deserialize_with` attribute:
+//! You can specify custom defaults for bare node names (nodes without arguments)
+//! using the provided helper functions:
//!
//! ```rust
//! use serde::Deserialize;
//!
//! #[derive(Deserialize)]
//! struct Config {
-//! #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+//! #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_true")]
//! enabled: bool,
//!
-//! #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
-//! disabled: bool,
+//! #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_false")]
+//! debug: 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)
+//! enabled // → true
+//! debug // → false
+//! enabled #false // → false (explicit override)
+//! debug #true // → true (explicit override)
//! ```
//!
+//! For other types, you can create custom deserializer functions following
+//! the same visitor pattern (see examples for details).
+//!
//! ### Nested Structs
//!
//! Nested structs use children blocks:
@@ -147,74 +150,89 @@ 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 {
+/// Serde helpers for custom defaults with bare node names.
+pub mod bare_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)
- }
+ /// Boolean-specific bare default deserializers.
+ pub mod bool {
+ use super::*;
- fn bare_default<'de, D, const DEFAULT: bool>(deserializer: D) -> Result<bool, D::Error>
- where
- D: Deserializer<'de>,
- {
- struct BareDefaultVisitor<const DEFAULT: bool>;
+ /// Deserializes a boolean field where bare node names default to `true`.
+ ///
+ /// Use with `#[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::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>,
+ {
+ deserialize_bool_with_bare_default(deserializer, true)
+ }
- impl<'de, const DEFAULT: bool> de::Visitor<'de> for BareDefaultVisitor<DEFAULT> {
- type Value = bool;
+ /// Deserializes a boolean field where bare node names default to `false`.
+ ///
+ /// Use with `#[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::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>,
+ {
+ deserialize_bool_with_bare_default(deserializer, false)
+ }
- fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(formatter, "a boolean value or bare node name")
+ fn deserialize_bool_with_bare_default<'de, D>(
+ deserializer: D,
+ default_value: bool,
+ ) -> Result<bool, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct BareDefaultBoolVisitor {
+ default_value: bool,
}
- fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
- where
- E: de::Error,
- {
- Ok(value)
- }
+ impl<'de> de::Visitor<'de> for BareDefaultBoolVisitor {
+ type Value = bool;
- fn visit_unit<E>(self) -> Result<Self::Value, E>
- where
- E: de::Error,
- {
- Ok(DEFAULT)
+ 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(self.default_value)
+ }
}
- }
- deserializer.deserialize_any(BareDefaultVisitor::<DEFAULT>)
+ deserializer.deserialize_any(BareDefaultBoolVisitor { default_value })
+ }
}
+
+ // For other types, users can create custom deserializer functions following
+ // the pattern demonstrated in the bool module above.
}
+
+
diff --git a/tests/integration_primitives.rs b/tests/integration_primitives.rs
index 434faaa..5a1f703 100644
--- a/tests/integration_primitives.rs
+++ b/tests/integration_primitives.rs
@@ -121,10 +121,10 @@ roundtrip!(
#[derive(Debug, Deserialize, PartialEq)]
struct BooleanDefaults {
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_true")]
enabled: bool,
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_false")]
disabled: bool,
}
@@ -175,7 +175,7 @@ deser_ok!(
fn boolean_defaults_single_bare_true() {
#[derive(Debug, Deserialize, PartialEq)]
struct W {
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_true")]
flag: bool,
}
let val: W = serde_kdl2::from_str("flag").unwrap();
@@ -187,7 +187,7 @@ fn boolean_defaults_single_bare_true() {
fn boolean_defaults_single_bare_false() {
#[derive(Debug, Deserialize, PartialEq)]
struct W {
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_false")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_false")]
flag: bool,
}
let val: W = serde_kdl2::from_str("flag").unwrap();
@@ -199,7 +199,7 @@ fn boolean_defaults_single_bare_false() {
fn boolean_defaults_explicit_false_with_bare_true() {
#[derive(Debug, Deserialize, PartialEq)]
struct W {
- #[serde(deserialize_with = "serde_kdl2::bool_defaults::bare_true")]
+ #[serde(deserialize_with = "serde_kdl2::bare_defaults::bool::bare_true")]
flag: bool,
}
let val: W = serde_kdl2::from_str("flag #false").unwrap();