From 09e159e3012245cd27d3d543619f49a3a5907b18 Mon Sep 17 00:00:00 2001 From: Jonas Spanoghe Date: Sat, 14 Feb 2026 07:53:10 +0100 Subject: [PATCH] Support configuration for serializer This adds a `to_string_with_config` method that allows passing configuration on how the value should be serialized. Currently the only config option is whether to write commented lines for `None` value or not. --- src/lib.rs | 33 ++++++++++++++++++++++++- src/ser.rs | 71 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 291b676..65a1ab3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -406,7 +406,7 @@ pub mod ser; pub use de::from_str; pub use error::Error; -pub use ser::to_string; +pub use ser::{to_string, to_string_with_config}; #[cfg(test)] mod tests { @@ -511,6 +511,37 @@ mod tests { assert!(lines.next().is_none()); } + #[test] + fn test_serialize_no_comments() { + #[derive(Debug, Serialize)] + struct Config { + street_name: Option, + house_number: Option, + zip_code: Option, + city: Option, + } + + let config = Config { + street_name: Some("Fakestreet".to_string()), + house_number: Some(123), + zip_code: None, + city: None, + }; + + let ini = to_string_with_config( + &config, + ser::Config { + none_as_comment: false, + }, + ) + .unwrap(); + let mut lines = ini.lines(); + + assert_eq!(lines.next(), Some("street_name = Fakestreet")); + assert_eq!(lines.next(), Some("house_number = 123")); + assert!(lines.next().is_none()); + } + #[test] fn test_deserialize_nested() { let ini_str = r#" diff --git a/src/ser.rs b/src/ser.rs index cf58320..20dfe5b 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,32 +1,58 @@ use crate::{Error, error::Result}; use serde::{Serialize, ser}; +#[derive(Default)] pub struct Serializer { output: String, current_section: Option, section_names: Vec, + config: Config, } -pub fn to_string(value: &T) -> Result -where - T: Serialize, -{ - let mut serializer = Serializer { - output: String::new(), - current_section: None, - section_names: Vec::new(), - }; +impl Serializer { + fn with_config(config: Config) -> Self { + Self { + config, + ..Default::default() + } + } + + fn serialize(mut self, value: &T) -> Result { + // First pass: collect all section names + let mut section_collector = SectionCollector { + sections: Vec::new(), + }; + value.serialize(&mut section_collector)?; + self.section_names = section_collector.sections; + + // Second pass: actual serialization + value.serialize(&mut self)?; + Ok(self.output) + } +} - // First pass: collect all section names - let mut section_collector = SectionCollector { - sections: Vec::new(), - }; - value.serialize(&mut section_collector)?; - serializer.section_names = section_collector.sections; +#[derive(Copy, Clone)] +pub struct Config { + /// Serialize `None` values serialized as commented lines + pub none_as_comment: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + none_as_comment: true, + } + } +} + +pub fn to_string(value: &T) -> Result { + let serializer = Serializer::default(); + serializer.serialize(value) +} - // Second pass: actual serialization - value.serialize(&mut serializer)?; - Ok(serializer.output) +pub fn to_string_with_config(value: &T, config: Config) -> Result { + let serializer = Serializer::with_config(config); + serializer.serialize(value) } // Helper to collect section names @@ -844,6 +870,7 @@ impl ser::SerializeStruct for &mut Serializer { output: String::new(), current_section: Some(key.to_string()), section_names: self.section_names.clone(), + config: self.config, }; value.serialize(&mut nested_serializer)?; @@ -855,14 +882,18 @@ impl ser::SerializeStruct for &mut Serializer { output: String::new(), current_section: self.current_section.clone(), section_names: self.section_names.clone(), + config: self.config, }; match value.serialize(&mut temp_serializer) { Ok(_) => { if temp_serializer.output.is_empty() { // This was None - // Skip commented lines for fields that are section names - if !self.section_names.contains(&key.to_string()) { + // Write out a commented line if configured to do so, + // and the field is not a section name + if self.config.none_as_comment + && !self.section_names.contains(&key.to_string()) + { self.write_commented_key(key); } } else {