From 1b39321ca62d00260cbef566e1e3b71fdc9eec4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Tue, 8 Oct 2024 22:44:00 -0700 Subject: [PATCH 1/6] add serde support wip --- Cargo.toml | 3 +- src/de.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 ++ src/se.rs | 1 + 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 src/de.rs create mode 100644 src/se.rs diff --git a/Cargo.toml b/Cargo.toml index 8b6d0c8..45f51ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ rust-version = "1.82" edition = "2021" [features] -default = ["span"] +default = ["span", "serde"] span = [] v1-fallback = ["v1"] v1 = ["kdlv1"] @@ -23,6 +23,7 @@ members = ["tools/*"] [dependencies] miette.workspace = true num = "0.4.2" +serde = { version = "1.0.210", optional = true } winnow = { version = "0.7.13", features = ["alloc", "unstable-recover"] } kdlv1 = { package = "kdl", version = "4.7.0", optional = true } diff --git a/src/de.rs b/src/de.rs new file mode 100644 index 0000000..a1a1d06 --- /dev/null +++ b/src/de.rs @@ -0,0 +1,235 @@ +use serde::{de, Deserialize}; +use thiserror::Error; +use winnow::{stream::Recoverable, Located}; + +use crate::{v2_parser::KdlParseError, KdlParseFailure}; + +/// serde deserializer for KDL documents +#[derive(Debug)] +pub struct Deserializer<'de> { + input: Recoverable, KdlParseError>, +} + +impl<'de> Deserializer<'de> { + /// Create a new deserializer from a string + pub fn from_str(input: &'de str) -> Self { + Self { + input: Recoverable::new(Located::new(input)), + } + } +} + +/// Deserialize a type from a KDL string +pub fn from_str<'a, T>(input: &'a str) -> Result +where + T: Deserialize<'a>, +{ +} + +#[derive(Debug, Error)] +struct DeError(String); + +impl std::fmt::Display for DeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl de::Error for DeError { + fn custom(msg: T) -> Self { + DeError(msg.to_string()) + } +} + +struct KdlVisitor; + +impl<'de> de::Visitor<'de> for KdlVisitor { + type Value = (); + + fn expecting<'a>(&self, formatter: &mut std::fmt::Formatter<'a>) -> std::fmt::Result { + write!(formatter, "a KDL value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + while let Some(key) = map.next_key()? { + match key { + "type" => { + let value = map.next_value::()?; + println!("type: {}", value); + } + "value" => { + let value = map.next_value::()?; + println!("value: {}", value); + } + _ => { + map.next_value::()?; + } + } + } + + Ok(()) + } +} + +impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { + type Error = DeError; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_unit_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_newtype_struct( + self, + name: &'static str, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_tuple_struct( + self, + name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs index fa1460a..a8c021b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,3 +209,8 @@ mod node; mod value; mod v2_parser; + +#[cfg(feature = "serde")] +pub mod de; +#[cfg(feature = "serde")] +pub mod se; diff --git a/src/se.rs b/src/se.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/se.rs @@ -0,0 +1 @@ + From e00e455115b5ee18f8587e1eaaacaea12fec8cea Mon Sep 17 00:00:00 2001 From: Miles Wirht <114884788+philocalyst@users.noreply.github.com> Date: Sun, 22 Mar 2026 22:09:00 -0400 Subject: [PATCH 2/6] feat(serde): serde support (#152) * serde: deserializaiton support * serde: serialziation implementation * serde: serialzaition docs * serde: serialization test * serde: deserialziation docs * serde: deserialziation tests * deps: added serde --- Cargo.toml | 1 + src/de.rs | 1465 ++++++++++++++++++++++++++++++++++++++++++++-------- src/se.rs | 1325 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2587 insertions(+), 204 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45f51ab..a6071d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ miette = { version = "7.6.0", default-features = false } miette = { workspace = true, features = ["derive", "fancy"] } thiserror = "2.0.12" pretty_assertions = "1.3.0" +serde = { version = "1.0.210", features = ["derive"] } # The profile that 'dist' will build with [profile.dist] diff --git a/src/de.rs b/src/de.rs index a1a1d06..b8fc307 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,235 +1,1292 @@ -use serde::{de, Deserialize}; -use thiserror::Error; -use winnow::{stream::Recoverable, Located}; +//! Serde deserializer for KDL documents. +//! +//! This module provides [`from_str`] for deserializing Rust types from KDL text. +//! +//! # KDL → Serde Data Model Mapping +//! +//! KDL documents are mapped to serde's data model as follows: +//! +//! - A **KDL document** is treated as a **map** (or struct), where each top-level +//! node name is a key and the node's content is the value. +//! - A **KDL node with only a single argument** and no properties/children is +//! treated as a **scalar value** (the argument itself). +//! - A **KDL node with multiple arguments** is treated as a **sequence** of those arguments. +//! - A **KDL node with properties** is treated as a **map** of property names to values. +//! - A **KDL node with children** is treated as a **map** (or struct), where child +//! node names are keys. +//! - A **KDL node with both properties and children** merges them into a single map. +//! - **Repeated node names** at the same level are collected into a **sequence**. +//! +//! # Example +//! +//! ```rust +//! use serde::Deserialize; +//! +//! #[derive(Deserialize, Debug, PartialEq)] +//! struct Config { +//! name: String, +//! port: u16, +//! } +//! +//! let kdl = r#" +//! name "my-app" +//! port 8080 +//! "#; +//! +//! let config: Config = kdl::de::from_str(kdl).unwrap(); +//! assert_eq!(config, Config { name: "my-app".into(), port: 8080 }); +//! ``` -use crate::{v2_parser::KdlParseError, KdlParseFailure}; +use std::fmt; -/// serde deserializer for KDL documents -#[derive(Debug)] -pub struct Deserializer<'de> { - input: Recoverable, KdlParseError>, +use serde::de::{self, DeserializeSeed, Deserializer as _, MapAccess, SeqAccess, Visitor}; +use serde::Deserialize; + +use crate::{KdlDocument, KdlEntry, KdlNode, KdlValue}; + +/// Errors that can occur during KDL deserialization. +#[derive(Debug, Clone)] +pub struct Error { + msg: String, } -impl<'de> Deserializer<'de> { - /// Create a new deserializer from a string - pub fn from_str(input: &'de str) -> Self { - Self { - input: Recoverable::new(Located::new(input)), +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.msg) + } +} + +impl std::error::Error for Error {} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error { + msg: msg.to_string(), } } } -/// Deserialize a type from a KDL string -pub fn from_str<'a, T>(input: &'a str) -> Result +impl From for Error { + fn from(e: crate::KdlError) -> Self { + Error { + msg: format!("{}", e), + } + } +} + +/// Helper to produce a `StrDeserializer` with our error type. +fn str_deserializer(s: &str) -> de::value::StrDeserializer<'_, Error> { + de::IntoDeserializer::into_deserializer(s) +} + +/// Deserialize a type from a KDL string. +/// +/// # Example +/// +/// ```rust +/// use serde::Deserialize; +/// +/// #[derive(Deserialize, Debug, PartialEq)] +/// struct Config { +/// name: String, +/// port: u16, +/// } +/// +/// let kdl = r#" +/// name "my-app" +/// port 8080 +/// "#; +/// +/// let config: Config = kdl::de::from_str(kdl).unwrap(); +/// assert_eq!(config, Config { name: "my-app".into(), port: 8080 }); +/// ``` +pub fn from_str<'a, T>(input: &'a str) -> Result where T: Deserialize<'a>, { + let doc: KdlDocument = input.parse().map_err(Error::from)?; + let de = DocumentDeserializer { doc: &doc }; + T::deserialize(de) } -#[derive(Debug, Error)] -struct DeError(String); - -impl std::fmt::Display for DeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } +struct ValueDeserializer<'a> { + value: &'a KdlValue, } -impl de::Error for DeError { - fn custom(msg: T) -> Self { - DeError(msg.to_string()) - } -} +impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> { + type Error = Error; -struct KdlVisitor; - -impl<'de> de::Visitor<'de> for KdlVisitor { - type Value = (); - - fn expecting<'a>(&self, formatter: &mut std::fmt::Formatter<'a>) -> std::fmt::Result { - write!(formatter, "a KDL value") - } - - fn visit_map(self, mut map: A) -> Result - where - A: de::MapAccess<'de>, - { - while let Some(key) = map.next_key()? { - match key { - "type" => { - let value = map.next_value::()?; - println!("type: {}", value); + fn deserialize_any>(self, visitor: V) -> Result { + match self.value { + KdlValue::String(s) => visitor.visit_str(s), + KdlValue::Integer(n) => { + if let Ok(i) = i64::try_from(*n) { + visitor.visit_i64(i) + } else if let Ok(u) = u64::try_from(*n) { + visitor.visit_u64(u) + } else { + visitor.visit_i128(*n) } - "value" => { - let value = map.next_value::()?; - println!("value: {}", value); + } + KdlValue::Float(f) => visitor.visit_f64(*f), + KdlValue::Bool(b) => visitor.visit_bool(*b), + KdlValue::Null => visitor.visit_unit(), + } + } + + fn deserialize_option>(self, visitor: V) -> Result { + match self.value { + KdlValue::Null => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_newtype_struct>( + self, + _name: &'static str, + visitor: V, + ) -> Result { + visitor.visit_newtype_struct(self) + } + + fn deserialize_enum>( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result { + match self.value { + KdlValue::String(s) => visitor.visit_enum(str_deserializer(s.as_str())), + _ => Err(de::Error::custom("expected a string for unit enum variant")), + } + } + + fn deserialize_string>(self, visitor: V) -> Result { + match self.value { + KdlValue::String(s) => visitor.visit_string(s.clone()), + _ => self.deserialize_any(visitor), + } + } + + fn deserialize_str>(self, visitor: V) -> Result { + match self.value { + KdlValue::String(s) => visitor.visit_str(s), + _ => self.deserialize_any(visitor), + } + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char + bytes byte_buf unit unit_struct seq tuple + tuple_struct map struct identifier ignored_any + } +} + +struct DocumentDeserializer<'a> { + doc: &'a KdlDocument, +} + +impl<'de, 'a> de::Deserializer<'de> for DocumentDeserializer<'a> { + type Error = Error; + + fn deserialize_any>(self, visitor: V) -> Result { + self.deserialize_map(visitor) + } + + fn deserialize_map>(self, visitor: V) -> Result { + visitor.visit_map(DocumentMapAccess::new(self.doc)) + } + + fn deserialize_struct>( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result { + self.deserialize_map(visitor) + } + + fn deserialize_seq>(self, visitor: V) -> Result { + visitor.visit_seq(NodeListSeqAccess { + iter: self.doc.nodes().iter(), + }) + } + + fn deserialize_option>(self, visitor: V) -> Result { + if self.doc.nodes().is_empty() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_newtype_struct>( + self, + _name: &'static str, + visitor: V, + ) -> Result { + visitor.visit_newtype_struct(self) + } + + fn deserialize_unit>(self, visitor: V) -> Result { + visitor.visit_unit() + } + + fn deserialize_unit_struct>( + self, + _name: &'static str, + visitor: V, + ) -> Result { + visitor.visit_unit() + } + + fn deserialize_enum>( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result { + // For enums at document level, treat as a single-node document where + // the node name is the variant. + let nodes = self.doc.nodes(); + if nodes.len() == 1 { + visitor.visit_enum(NodeEnumAccess { node: &nodes[0] }) + } else { + Err(de::Error::custom( + "expected exactly one node for enum deserialization", + )) + } + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 + i64 i128 u8 u16 + u32 u64 u128 f32 + f64 char str string + bytes byte_buf tuple + tuple_struct identifier ignored_any + } +} + +/// Groups nodes by name, producing deduplicated keys. Nodes that share a name +/// are collected into a Vec so they can be deserialized as sequences. +struct DocumentMapAccess<'a> { + /// Ordered unique keys (node names in first-seen order). + keys: Vec<&'a str>, + + /// All nodes grouped by name, preserving order within each group. + groups: std::collections::HashMap<&'a str, Vec<&'a KdlNode>>, + + /// Current index into `keys`. + idx: usize, +} + +impl<'a> DocumentMapAccess<'a> { + fn new(doc: &'a KdlDocument) -> Self { + let mut keys = Vec::new(); + let mut groups: std::collections::HashMap<&'a str, Vec<&'a KdlNode>> = + std::collections::HashMap::new(); + for node in doc.nodes() { + let name = node.name().value(); + if !groups.contains_key(name) { + keys.push(name); + } + groups.entry(name).or_default().push(node); + } + DocumentMapAccess { + keys, + groups, + idx: 0, + } + } +} + +impl<'de, 'a> MapAccess<'de> for DocumentMapAccess<'a> { + type Error = Error; + + fn next_key_seed>( + &mut self, + seed: K, + ) -> Result, Self::Error> { + if self.idx >= self.keys.len() { + return Ok(None); + } + let key = self.keys[self.idx]; + seed.deserialize(str_deserializer(key)).map(Some) + } + + fn next_value_seed>( + &mut self, + seed: V, + ) -> Result { + let key = self.keys[self.idx]; + self.idx += 1; + let nodes = self.groups.get(key).unwrap(); + if nodes.len() == 1 { + seed.deserialize(NodeDeserializer { node: nodes[0] }) + } else { + // Multiple nodes with same name → sequence + seed.deserialize(NodeGroupDeserializer { nodes }) + } + } +} + +struct NodeDeserializer<'a> { + node: &'a KdlNode, +} + +impl<'a> NodeDeserializer<'a> { + fn args(&self) -> Vec<&'a KdlEntry> { + self.node + .entries() + .iter() + .filter(|e| e.name().is_none()) + .collect() + } + + fn props(&self) -> Vec<&'a KdlEntry> { + self.node + .entries() + .iter() + .filter(|e| e.name().is_some()) + .collect() + } + + /// Returns true if this node has only a single argument and nothing else. + fn is_scalar(&self) -> bool { + let args = self.args(); + let props = self.props(); + args.len() == 1 && props.is_empty() && self.node.children().is_none() + } + + /// Returns true if this node is "empty" (no args, no props, no children). + fn is_empty(&self) -> bool { + self.node.entries().is_empty() && self.node.children().is_none() + } +} + +impl<'de, 'a> de::Deserializer<'de> for NodeDeserializer<'a> { + type Error = Error; + + fn deserialize_any>(self, visitor: V) -> Result { + if self.is_empty() { + // Node with no data → null/unit + return visitor.visit_unit(); + } + if self.is_scalar() { + // Single argument → deserialize as scalar + let entry = self.args()[0]; + return ValueDeserializer { + value: entry.value(), + } + .deserialize_any(visitor); + } + + let args = self.args(); + let props = self.props(); + let has_children = self.node.children().is_some(); + + if !args.is_empty() && props.is_empty() && !has_children { + // Only positional args → sequence + return visitor.visit_seq(ArgSeqAccess { + iter: args.into_iter(), + }); + } + + // Has properties and/or children → map + self.deserialize_map(visitor) + } + + fn deserialize_bool>(self, visitor: V) -> Result { + if self.is_scalar() { + ValueDeserializer { + value: self.args()[0].value(), + } + .deserialize_any(visitor) + } else { + Err(de::Error::custom("expected a boolean value")) + } + } + + fn deserialize_i8>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_i16>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_i32>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_i64>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_i128>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_u8>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_u16>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_u32>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_u64>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_u128>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_f32>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_f64>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_char>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_str>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_string>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_bytes>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + fn deserialize_byte_buf>(self, visitor: V) -> Result { + self.deserialize_scalar(visitor) + } + + fn deserialize_option>(self, visitor: V) -> Result { + if self.is_empty() { + visitor.visit_none() + } else if self.is_scalar() { + let val = self.args()[0].value(); + if matches!(val, KdlValue::Null) { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } else { + visitor.visit_some(self) + } + } + + fn deserialize_unit>(self, visitor: V) -> Result { + visitor.visit_unit() + } + + fn deserialize_unit_struct>( + self, + _name: &'static str, + visitor: V, + ) -> Result { + visitor.visit_unit() + } + + fn deserialize_newtype_struct>( + self, + _name: &'static str, + visitor: V, + ) -> Result { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq>(self, visitor: V) -> Result { + let args = self.args(); + + if !args.is_empty() && self.node.children().is_none() { + // Arguments → sequence + visitor.visit_seq(ArgSeqAccess { + iter: args.into_iter(), + }) + } else if let Some(children) = self.node.children() { + // Children → sequence of nodes + visitor.visit_seq(NodeListSeqAccess { + iter: children.nodes().iter(), + }) + } else { + // Empty → empty sequence + visitor.visit_seq(ArgSeqAccess { + iter: Vec::new().into_iter(), + }) + } + } + + fn deserialize_tuple>( + self, + _len: usize, + visitor: V, + ) -> Result { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct>( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result { + self.deserialize_seq(visitor) + } + + fn deserialize_map>(self, visitor: V) -> Result { + visitor.visit_map(NodeMapAccess::new(self.node)) + } + + fn deserialize_struct>( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result { + self.deserialize_map(visitor) + } + + fn deserialize_enum>( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result { + // If the node is a scalar string, treat it as a unit variant name. + if self.is_scalar() { + if let KdlValue::String(s) = self.args()[0].value() { + return visitor.visit_enum(str_deserializer(s.as_str())); + } + } + + // Otherwise, try treating the node as an externally-tagged enum: + // if there's exactly one child node, use its name as variant, body as data. + if let Some(children) = self.node.children() { + let child_nodes = children.nodes(); + if child_nodes.len() == 1 { + return visitor.visit_enum(NodeEnumAccess { + node: &child_nodes[0], + }); + } + } + + // For nodes with properties but no children, try MAP-STYLE + let props = self.props(); + if props.len() == 1 && self.args().is_empty() && self.node.children().is_none() { + let prop = props[0]; + let name = prop.name().unwrap().value(); + return visitor.visit_enum(PropertyEnumAccess { + key: name, + value: prop.value(), + }); + } + + // Fall back: just node name is variant + visitor.visit_enum(NodeEnumAccess { node: self.node }) + } + + fn deserialize_identifier>(self, visitor: V) -> Result { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any>(self, visitor: V) -> Result { + visitor.visit_unit() + } +} + +impl<'a> NodeDeserializer<'a> { + fn deserialize_scalar<'de, V: Visitor<'de>>(self, visitor: V) -> Result { + if self.is_scalar() { + ValueDeserializer { + value: self.args()[0].value(), + } + .deserialize_any(visitor) + } else { + self.deserialize_any(visitor) + } + } +} + +struct ArgSeqAccess<'a> { + iter: std::vec::IntoIter<&'a KdlEntry>, +} + +impl<'de, 'a> SeqAccess<'de> for ArgSeqAccess<'a> { + type Error = Error; + + fn next_element_seed>( + &mut self, + seed: T, + ) -> Result, Self::Error> { + match self.iter.next() { + Some(entry) => seed + .deserialize(ValueDeserializer { + value: entry.value(), + }) + .map(Some), + None => Ok(None), + } + } +} + +struct NodeListSeqAccess<'a> { + iter: std::slice::Iter<'a, KdlNode>, +} + +impl<'de, 'a> SeqAccess<'de> for NodeListSeqAccess<'a> { + type Error = Error; + + fn next_element_seed>( + &mut self, + seed: T, + ) -> Result, Self::Error> { + match self.iter.next() { + Some(node) => seed.deserialize(NodeDeserializer { node }).map(Some), + None => Ok(None), + } + } +} + +struct NodeGroupDeserializer<'a> { + nodes: &'a [&'a KdlNode], +} + +impl<'de, 'a> de::Deserializer<'de> for NodeGroupDeserializer<'a> { + type Error = Error; + + fn deserialize_any>(self, visitor: V) -> Result { + self.deserialize_seq(visitor) + } + + fn deserialize_seq>(self, visitor: V) -> Result { + visitor.visit_seq(NodeGroupSeqAccess { + iter: self.nodes.iter(), + }) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf option unit unit_struct newtype_struct tuple + tuple_struct map struct enum identifier ignored_any + } +} + +struct NodeGroupSeqAccess<'a> { + iter: std::slice::Iter<'a, &'a KdlNode>, +} + +impl<'de, 'a> SeqAccess<'de> for NodeGroupSeqAccess<'a> { + type Error = Error; + + fn next_element_seed>( + &mut self, + seed: T, + ) -> Result, Self::Error> { + match self.iter.next() { + Some(node) => seed.deserialize(NodeDeserializer { node }).map(Some), + None => Ok(None), + } + } +} + +struct NodeMapAccess<'a> { + /// Properties and children combined as (key, value_source). + entries: Vec<(&'a str, NodeMapValue<'a>)>, + idx: usize, +} + +enum NodeMapValue<'a> { + Arg(&'a KdlValue), + SingleNode(&'a KdlNode), + MultiNode(Vec<&'a KdlNode>), +} + +impl<'a> NodeMapAccess<'a> { + fn new(node: &'a KdlNode) -> Self { + let mut entries: Vec<(&'a str, NodeMapValue<'a>)> = Vec::new(); + + // Add the first argument as "-" if there are args and also props/children + // (for mixed nodes). Otherwise arguments are not included in map mode. + let args: Vec<_> = node + .entries() + .iter() + .filter(|e| e.name().is_none()) + .collect(); + let props: Vec<_> = node + .entries() + .iter() + .filter(|e| e.name().is_some()) + .collect(); + + // If there are only args and no props/children, we shouldn't be in map mode. + // But if we are (struct requested), put args as "-" entries. + if !args.is_empty() && (props.is_empty() && node.children().is_none()) { + for arg in args.iter() { + entries.push(("-", NodeMapValue::Arg(arg.value()))); + } + } + + // Add properties + for prop in &props { + let name = prop.name().unwrap().value(); + entries.push((name, NodeMapValue::Arg(prop.value()))); + } + + // Add children (grouped by node name) + if let Some(children) = node.children() { + let mut child_keys: Vec<&str> = Vec::new(); + let mut child_groups: std::collections::HashMap<&str, Vec<&KdlNode>> = + std::collections::HashMap::new(); + for child in children.nodes() { + let name = child.name().value(); + if !child_groups.contains_key(name) { + child_keys.push(name); } - _ => { - map.next_value::()?; + child_groups.entry(name).or_default().push(child); + } + for key in child_keys { + let group = child_groups.remove(key).unwrap(); + if group.len() == 1 { + entries.push((key, NodeMapValue::SingleNode(group[0]))); + } else { + entries.push((key, NodeMapValue::MultiNode(group))); } } } + NodeMapAccess { entries, idx: 0 } + } +} + +impl<'de, 'a> MapAccess<'de> for NodeMapAccess<'a> { + type Error = Error; + + fn next_key_seed>( + &mut self, + seed: K, + ) -> Result, Self::Error> { + if self.idx >= self.entries.len() { + return Ok(None); + } + let key = self.entries[self.idx].0; + seed.deserialize(str_deserializer(key)).map(Some) + } + + fn next_value_seed>( + &mut self, + seed: V, + ) -> Result { + let (_, ref value) = self.entries[self.idx]; + self.idx += 1; + match value { + NodeMapValue::Arg(v) => seed.deserialize(ValueDeserializer { value: v }), + NodeMapValue::SingleNode(node) => seed.deserialize(NodeDeserializer { node }), + NodeMapValue::MultiNode(nodes) => seed.deserialize(NodeGroupDeserializer { nodes }), + } + } +} + +struct NodeEnumAccess<'a> { + node: &'a KdlNode, +} + +impl<'de, 'a> de::EnumAccess<'de> for NodeEnumAccess<'a> { + type Error = Error; + type Variant = NodeVariantAccess<'a>; + + fn variant_seed>( + self, + seed: V, + ) -> Result<(V::Value, Self::Variant), Self::Error> { + let variant_name = self.node.name().value(); + let val = seed.deserialize(str_deserializer(variant_name))?; + Ok((val, NodeVariantAccess { node: self.node })) + } +} + +struct NodeVariantAccess<'a> { + node: &'a KdlNode, +} + +impl<'de, 'a> de::VariantAccess<'de> for NodeVariantAccess<'a> { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { Ok(()) } + + fn newtype_variant_seed>( + self, + seed: T, + ) -> Result { + seed.deserialize(NodeDeserializer { node: self.node }) + } + + fn tuple_variant>( + self, + _len: usize, + visitor: V, + ) -> Result { + de::Deserializer::deserialize_seq(NodeDeserializer { node: self.node }, visitor) + } + + fn struct_variant>( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result { + de::Deserializer::deserialize_map(NodeDeserializer { node: self.node }, visitor) + } } -impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { - type Error = DeError; +struct PropertyEnumAccess<'a> { + key: &'a str, + value: &'a KdlValue, +} - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - self.deserialize_map(visitor) - } +impl<'de, 'a> de::EnumAccess<'de> for PropertyEnumAccess<'a> { + type Error = Error; + type Variant = PropertyVariantAccess<'a>; - fn deserialize_bool(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_char(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_string(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_unit(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_unit_struct( + fn variant_seed>( self, - name: &'static str, - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_newtype_struct( - self, - name: &'static str, - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_tuple(self, len: usize, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_tuple_struct( - self, - name: &'static str, - len: usize, - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_map(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_struct( - self, - name: &'static str, - fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_enum( - self, - name: &'static str, - variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() - } - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - todo!() + seed: V, + ) -> Result<(V::Value, Self::Variant), Self::Error> { + let val = seed.deserialize(str_deserializer(self.key))?; + Ok((val, PropertyVariantAccess { value: self.value })) + } +} + +struct PropertyVariantAccess<'a> { + value: &'a KdlValue, +} + +impl<'de, 'a> de::VariantAccess<'de> for PropertyVariantAccess<'a> { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed>( + self, + seed: T, + ) -> Result { + seed.deserialize(ValueDeserializer { value: self.value }) + } + + fn tuple_variant>( + self, + _len: usize, + _visitor: V, + ) -> Result { + Err(de::Error::custom( + "tuple variants not supported from KDL properties", + )) + } + + fn struct_variant>( + self, + _fields: &'static [&'static str], + _visitor: V, + ) -> Result { + Err(de::Error::custom( + "struct variants not supported from KDL properties", + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::Deserialize; + + #[test] + fn simple_struct() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + name: String, + port: u16, + } + + let kdl = r#" +name "my-app" +port 8080 +"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + name: "my-app".into(), + port: 8080, + } + ); + } + + #[test] + fn nested_struct() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + server: Server, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Server { + host: String, + port: u16, + } + + let kdl = r#" +server { + host "localhost" + port 8080 +} +"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + server: Server { + host: "localhost".into(), + port: 8080, + }, + } + ); + } + + #[test] + fn node_with_properties() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + server: Server, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Server { + host: String, + port: u16, + } + + let kdl = r#"server host="localhost" port=8080"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + server: Server { + host: "localhost".into(), + port: 8080, + }, + } + ); + } + + #[test] + fn sequence_of_args() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + values: Vec, + } + + let kdl = r#"values 1 2 3"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + values: vec![1, 2, 3] + } + ); + } + + #[test] + fn repeated_nodes_as_seq() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + route: Vec, + } + + let kdl = r#" +route "/api/users" +route "/api/posts" +route "/api/comments" +"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + route: vec![ + "/api/users".into(), + "/api/posts".into(), + "/api/comments".into(), + ], + } + ); + } + + #[test] + fn boolean_and_null() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + enabled: bool, + disabled: bool, + nothing: Option, + } + + let kdl = r#" +enabled #true +disabled #false +nothing #null +"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + enabled: true, + disabled: false, + nothing: None, + } + ); + } + + #[test] + fn option_some() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + name: Option, + } + + let kdl = r#"name "hello""#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + name: Some("hello".into()), + } + ); + } + + #[test] + fn float_values() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + ratio: f64, + } + + let kdl = r#"ratio 3.14"#; + let config: Config = from_str(kdl).unwrap(); + assert!((config.ratio - 3.14).abs() < f64::EPSILON); + } + + #[test] + fn enum_unit_variant() { + #[derive(Deserialize, Debug, PartialEq)] + enum Color { + Red, + Green, + Blue, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + color: Color, + } + + let kdl = r#"color "Red""#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!(config, Config { color: Color::Red }); + } + + #[test] + fn deeply_nested() { + #[derive(Deserialize, Debug, PartialEq)] + struct Root { + level1: Level1, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Level1 { + level2: Level2, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Level2 { + value: i32, + } + + let kdl = r#" +level1 { + level2 { + value 42 + } +} +"#; + let root: Root = from_str(kdl).unwrap(); + assert_eq!( + root, + Root { + level1: Level1 { + level2: Level2 { value: 42 }, + }, + } + ); + } + + #[test] + fn children_as_seq() { + #[derive(Deserialize, Debug, PartialEq)] + struct Item { + name: String, + } + + let kdl = r#" +items { + item { + name "a" + } + item { + name "b" + } +} +"#; + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + items: Items, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Items { + item: Vec, + } + + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + items: Items { + item: vec![Item { name: "a".into() }, Item { name: "b".into() },], + }, + } + ); + } + + #[test] + fn mixed_props_and_children() { + #[derive(Deserialize, Debug, PartialEq)] + struct Server { + host: String, + port: u16, + routes: Routes, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Routes { + path: Vec, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + server: Server, + } + + let kdl = r#" +server host="localhost" port=8080 { + routes { + path "/a" + path "/b" + } +} +"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + server: Server { + host: "localhost".into(), + port: 8080, + routes: Routes { + path: vec!["/a".into(), "/b".into()], + }, + }, + } + ); + } + + #[test] + fn empty_document() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config {} + + let config: Config = from_str("").unwrap(); + assert_eq!(config, Config {}); + } + + #[test] + fn hashmap() { + use std::collections::HashMap; + + let kdl = r#" +alpha 1 +beta 2 +gamma 3 +"#; + let map: HashMap = from_str(kdl).unwrap(); + assert_eq!(map.get("alpha"), Some(&1)); + assert_eq!(map.get("beta"), Some(&2)); + assert_eq!(map.get("gamma"), Some(&3)); + } + + #[test] + fn newtype_struct() { + #[derive(Deserialize, Debug, PartialEq)] + struct Port(u16); + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + port: Port, + } + + let kdl = r#"port 8080"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!(config, Config { port: Port(8080) }); + } + + #[test] + fn integer_types() { + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + a: i8, + b: i16, + c: i32, + d: i64, + e: u8, + f: u16, + g: u32, + h: u64, + } + + let kdl = r#" +a 1 +b 2 +c 3 +d 4 +e 5 +f 6 +g 7 +h 8 +"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + a: 1, + b: 2, + c: 3, + d: 4, + e: 5, + f: 6, + g: 7, + h: 8, + } + ); } } diff --git a/src/se.rs b/src/se.rs index 8b13789..a08e60b 100644 --- a/src/se.rs +++ b/src/se.rs @@ -1 +1,1326 @@ +//! Serde serializer for KDL documents. +//! +//! This module provides [`to_string`] for serializing Rust types to KDL text. +//! +//! # KDL Serialization Model +//! +//! Rust types are mapped to KDL as follows: +//! +//! - **Structs**: Each field becomes a KDL node at the current level, where the +//! node name is the field name and the node's first argument is the field value. +//! - **Maps**: Same as structs — each key becomes a node name. +//! - **Sequences/Tuples**: Serialized as child nodes named `-` (the KDL dash convention). +//! - **Enums**: Unit variants are serialized as bare string arguments. Newtype, tuple, +//! and struct variants use the variant name as a node name. +//! - **Options**: `None` is serialized as `#null`, `Some(v)` serializes `v` directly. +//! - **Scalars**: Serialized as KDL values (strings, integers, floats, booleans). +//! +//! # Example +//! +//! ```rust +//! use serde::Serialize; +//! +//! #[derive(Serialize)] +//! struct Config { +//! name: String, +//! port: u16, +//! } +//! +//! let config = Config { name: "my-app".into(), port: 8080 }; +//! let kdl = kdl::se::to_string(&config).unwrap(); +//! assert_eq!(kdl, "name \"my-app\"\nport 8080\n"); +//! ``` +use serde::ser::{self, Serialize}; +use std::fmt; + +use crate::{KdlDocument, KdlEntry, KdlEntryFormat, KdlNode, KdlValue}; + +/// Errors that can occur during KDL serialization. +#[derive(Debug)] +pub struct Error { + msg: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.msg) + } +} + +impl std::error::Error for Error {} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error { + msg: msg.to_string(), + } + } +} + +/// Serialize a value to a KDL string. +/// +/// # Example +/// +/// ```rust +/// use serde::Serialize; +/// +/// #[derive(Serialize)] +/// struct Config { +/// name: String, +/// port: u16, +/// } +/// +/// let config = Config { name: "my-app".into(), port: 8080 }; +/// let kdl = kdl::se::to_string(&config).unwrap(); +/// assert_eq!(kdl, "name \"my-app\"\nport 8080\n"); +/// ``` +pub fn to_string(value: &T) -> Result { + let doc = to_document(value)?; + Ok(doc.to_string()) +} + +/// Serialize a value to a [`KdlDocument`]. +/// +/// This is useful if you want to manipulate the document before converting +/// it to a string. +pub fn to_document(value: &T) -> Result { + let mut ser = DocumentSerializer { + doc: KdlDocument::new(), + }; + value.serialize(&mut ser)?; + Ok(ser.doc) +} + +struct DocumentSerializer { + doc: KdlDocument, +} + +impl<'a> ser::Serializer for &'a mut DocumentSerializer { + type Ok = (); + type Error = Error; + + type SerializeSeq = SeqNodeSerializer<'a>; + type SerializeTuple = SeqNodeSerializer<'a>; + type SerializeTupleStruct = SeqNodeSerializer<'a>; + type SerializeTupleVariant = SeqNodeSerializer<'a>; + type SerializeMap = MapNodeSerializer<'a>; + type SerializeStruct = MapNodeSerializer<'a>; + type SerializeStructVariant = StructVariantSerializer<'a>; + + fn serialize_bool(self, v: bool) -> Result { + self.doc.nodes_mut().push(value_node("-", v.into())); + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i64(self, v: i64) -> Result { + self.doc + .nodes_mut() + .push(value_node("-", KdlValue::Integer(v as i128))); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u16(self, v: u16) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u32(self, v: u32) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u64(self, v: u64) -> Result { + self.doc + .nodes_mut() + .push(value_node("-", KdlValue::Integer(v as i128))); + Ok(()) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(v as f64) + } + fn serialize_f64(self, v: f64) -> Result { + self.doc + .nodes_mut() + .push(value_node("-", KdlValue::Float(v))); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + let mut entry = KdlEntry::new(KdlValue::String(v.to_string())); + entry.set_format(KdlEntryFormat { + value_repr: format!("\"{}\"", v.escape_default()), + leading: " ".to_string(), + ..Default::default() + }); + let mut node = KdlNode::new("-"); + node.entries_mut().push(entry); + self.doc.nodes_mut().push(node); + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + use serde::ser::SerializeSeq; + let mut seq = self.serialize_seq(Some(v.len()))?; + for b in v { + seq.serialize_element(b)?; + } + seq.end() + } + + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + // Empty document for unit + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.doc + .nodes_mut() + .push(value_node("-", KdlValue::String(variant.to_string()))); + Ok(()) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + let mut node = KdlNode::new(variant); + let kdl_val = to_kdl_value(value)?; + node.entries_mut().push(KdlEntry::new(kdl_val)); + self.doc.nodes_mut().push(node); + Ok(()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Ok(SeqNodeSerializer { + nodes: &mut self.doc.nodes, + node_name: "-", + }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(SeqNodeSerializer { + nodes: &mut self.doc.nodes, + node_name: variant, + }) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(MapNodeSerializer { + nodes: &mut self.doc.nodes, + current_key: None, + }) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(MapNodeSerializer { + nodes: &mut self.doc.nodes, + current_key: None, + }) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(StructVariantSerializer { + parent_nodes: &mut self.doc.nodes, + variant, + children: KdlDocument::new(), + }) + } +} + +struct SeqNodeSerializer<'a> { + nodes: &'a mut Vec, + node_name: &'a str, +} + +impl<'a> ser::SerializeSeq for SeqNodeSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + let mut node = KdlNode::new(self.node_name); + serialize_value_into_node(&mut node, value)?; + self.nodes.push(node); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::SerializeTuple for SeqNodeSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl<'a> ser::SerializeTupleStruct for SeqNodeSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl<'a> ser::SerializeTupleVariant for SeqNodeSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +struct MapNodeSerializer<'a> { + nodes: &'a mut Vec, + current_key: Option, +} + +impl<'a> ser::SerializeMap for MapNodeSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> { + self.current_key = Some(key_to_string(key)?); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> { + let key = self + .current_key + .take() + .ok_or_else(|| ser::Error::custom("serialize_value without serialize_key"))?; + let mut node = KdlNode::new(key); + serialize_value_into_node(&mut node, value)?; + self.nodes.push(node); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::SerializeStruct for MapNodeSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + let mut node = KdlNode::new(key); + serialize_value_into_node(&mut node, value)?; + self.nodes.push(node); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +struct StructVariantSerializer<'a> { + parent_nodes: &'a mut Vec, + variant: &'static str, + children: KdlDocument, +} + +impl<'a> ser::SerializeStructVariant for StructVariantSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + let mut node = KdlNode::new(key); + serialize_value_into_node(&mut node, value)?; + self.children.nodes_mut().push(node); + Ok(()) + } + + fn end(self) -> Result { + let mut node = KdlNode::new(self.variant); + node.set_children(self.children); + self.parent_nodes.push(node); + Ok(()) + } +} + +/// Serializes a value into a node, adding it as an argument, or as children +/// if the value is complex (struct/map/seq). +fn serialize_value_into_node( + node: &mut KdlNode, + value: &T, +) -> Result<(), Error> { + let mut ser = NodeValueSerializer { node }; + value.serialize(&mut ser) +} + +/// Serializer that writes into a KdlNode. +struct NodeValueSerializer<'a> { + node: &'a mut KdlNode, +} + +impl<'a> ser::Serializer for &'a mut NodeValueSerializer<'a> { + type Ok = (); + type Error = Error; + + type SerializeSeq = NodeSeqSerializer<'a>; + type SerializeTuple = NodeSeqSerializer<'a>; + type SerializeTupleStruct = NodeSeqSerializer<'a>; + type SerializeTupleVariant = NodeChildSeqSerializer<'a>; + type SerializeMap = NodeChildMapSerializer<'a>; + type SerializeStruct = NodeChildMapSerializer<'a>; + type SerializeStructVariant = NodeChildStructVariantSerializer<'a>; + + fn serialize_bool(self, v: bool) -> Result { + self.node.entries_mut().push(KdlEntry::new(v)); + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i64(self, v: i64) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::Integer(v as i128))); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u16(self, v: u16) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u32(self, v: u32) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u64(self, v: u64) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::Integer(v as i128))); + Ok(()) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(v as f64) + } + fn serialize_f64(self, v: f64) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::Float(v))); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + let mut entry = KdlEntry::new(KdlValue::String(v.to_string())); + entry.set_format(KdlEntryFormat { + value_repr: format!("\"{}\"", v.escape_default()), + leading: " ".to_string(), + ..Default::default() + }); + self.node.entries_mut().push(entry); + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + use serde::ser::SerializeSeq; + let mut seq = self.serialize_seq(Some(v.len()))?; + for b in v { + seq.serialize_element(b)?; + } + seq.end() + } + + fn serialize_none(self) -> Result { + self.node.entries_mut().push(KdlEntry::new(KdlValue::Null)); + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + self.node.entries_mut().push(KdlEntry::new(KdlValue::Null)); + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + let mut child = KdlNode::new(variant); + let kdl_val = to_kdl_value(value)?; + child.entries_mut().push(KdlEntry::new(kdl_val)); + let children = self.node.ensure_children(); + children.nodes_mut().push(child); + Ok(()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Ok(NodeSeqSerializer { node: self.node }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(NodeChildSeqSerializer { + parent: self.node, + variant_name: Some(variant), + items: Vec::new(), + }) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(NodeChildMapSerializer { + node: self.node, + current_key: None, + }) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(NodeChildMapSerializer { + node: self.node, + current_key: None, + }) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(NodeChildStructVariantSerializer { + parent: self.node, + variant, + children: KdlDocument::new(), + }) + } +} + +struct NodeSeqSerializer<'a> { + node: &'a mut KdlNode, +} + +impl<'a> ser::SerializeSeq for NodeSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + let kdl_val = to_kdl_value(value)?; + self.node.entries_mut().push(KdlEntry::new(kdl_val)); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::SerializeTuple for NodeSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl<'a> ser::SerializeTupleStruct for NodeSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +struct NodeChildSeqSerializer<'a> { + parent: &'a mut KdlNode, + variant_name: Option<&'static str>, + items: Vec, +} + +impl<'a> ser::SerializeTupleVariant for NodeChildSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + let kdl_val = to_kdl_value(value)?; + self.items.push(kdl_val); + Ok(()) + } + + fn end(self) -> Result { + let name = self.variant_name.unwrap_or("-"); + let mut node = KdlNode::new(name); + for item in self.items { + node.entries_mut().push(KdlEntry::new(item)); + } + let children = self.parent.ensure_children(); + children.nodes_mut().push(node); + Ok(()) + } +} + +struct NodeChildMapSerializer<'a> { + node: &'a mut KdlNode, + current_key: Option, +} + +impl<'a> ser::SerializeMap for NodeChildMapSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> { + self.current_key = Some(key_to_string(key)?); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> { + let key = self + .current_key + .take() + .ok_or_else(|| ser::Error::custom("serialize_value without serialize_key"))?; + let mut child = KdlNode::new(key); + let mut child_ser = NodeValueSerializer { node: &mut child }; + value.serialize(&mut child_ser)?; + let children = self.node.ensure_children(); + children.nodes_mut().push(child); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::SerializeStruct for NodeChildMapSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + let mut child = KdlNode::new(key); + let mut child_ser = NodeValueSerializer { node: &mut child }; + value.serialize(&mut child_ser)?; + let children = self.node.ensure_children(); + children.nodes_mut().push(child); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +struct NodeChildStructVariantSerializer<'a> { + parent: &'a mut KdlNode, + variant: &'static str, + children: KdlDocument, +} + +impl<'a> ser::SerializeStructVariant for NodeChildStructVariantSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + let mut child = KdlNode::new(key); + let mut child_ser = NodeValueSerializer { node: &mut child }; + value.serialize(&mut child_ser)?; + self.children.nodes_mut().push(child); + Ok(()) + } + + fn end(self) -> Result { + let mut node = KdlNode::new(self.variant); + node.set_children(self.children); + let parent_children = self.parent.ensure_children(); + parent_children.nodes_mut().push(node); + Ok(()) + } +} + +/// Helper for a regular node with just a name and just an argument value +fn value_node(name: &str, value: KdlValue) -> KdlNode { + let mut node = KdlNode::new(name); + node.entries_mut().push(KdlEntry::new(value)); + node +} + +/// Convert a serde key to a string. +fn key_to_string(key: &T) -> Result { + key.serialize(KeySerializer) +} + +/// Restricted serializer that only produces strings (for map keys / node names). +struct KeySerializer; + +impl ser::Serializer for KeySerializer { + type Ok = String; + type Error = Error; + + type SerializeSeq = ser::Impossible; + type SerializeTuple = ser::Impossible; + type SerializeTupleStruct = ser::Impossible; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = ser::Impossible; + type SerializeStruct = ser::Impossible; + type SerializeStructVariant = ser::Impossible; + + fn serialize_bool(self, v: bool) -> Result { + Ok(if v { + "true".to_string() + } else { + "false".to_string() + }) + } + + fn serialize_i8(self, v: i8) -> Result { + Ok(v.to_string()) + } + fn serialize_i16(self, v: i16) -> Result { + Ok(v.to_string()) + } + fn serialize_i32(self, v: i32) -> Result { + Ok(v.to_string()) + } + fn serialize_i64(self, v: i64) -> Result { + Ok(v.to_string()) + } + fn serialize_u8(self, v: u8) -> Result { + Ok(v.to_string()) + } + fn serialize_u16(self, v: u16) -> Result { + Ok(v.to_string()) + } + fn serialize_u32(self, v: u32) -> Result { + Ok(v.to_string()) + } + fn serialize_u64(self, v: u64) -> Result { + Ok(v.to_string()) + } + + fn serialize_f32(self, v: f32) -> Result { + Ok(v.to_string()) + } + fn serialize_f64(self, v: f64) -> Result { + Ok(v.to_string()) + } + + fn serialize_char(self, v: char) -> Result { + Ok(v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(v.to_string()) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(ser::Error::custom("bytes cannot be used as KDL node names")) + } + + fn serialize_none(self) -> Result { + Err(ser::Error::custom("None cannot be used as a KDL node name")) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Err(ser::Error::custom("unit cannot be used as a KDL node name")) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + Ok(name.to_string()) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + Ok(variant.to_string()) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result { + Err(ser::Error::custom( + "newtype variants cannot be used as KDL node names", + )) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(ser::Error::custom( + "sequences cannot be used as KDL node names", + )) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(ser::Error::custom( + "tuples cannot be used as KDL node names", + )) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "tuple structs cannot be used as KDL node names", + )) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "tuple variants cannot be used as KDL node names", + )) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(ser::Error::custom("maps cannot be used as KDL node names")) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "structs cannot be used as KDL node names", + )) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "struct variants cannot be used as KDL node names", + )) + } +} + +/// Try to convert a serde-serializable value to a KdlValue. +/// Retreats a to String for complex types. +fn to_kdl_value(value: &T) -> Result { + value.serialize(KdlValueSerializer) +} + +/// Serializer that produces a KdlValue from a scalar. +struct KdlValueSerializer; + +impl ser::Serializer for KdlValueSerializer { + type Ok = KdlValue; + type Error = Error; + + type SerializeSeq = ser::Impossible; + type SerializeTuple = ser::Impossible; + type SerializeTupleStruct = ser::Impossible; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = ser::Impossible; + type SerializeStruct = ser::Impossible; + type SerializeStructVariant = ser::Impossible; + + fn serialize_bool(self, v: bool) -> Result { + Ok(KdlValue::Bool(v)) + } + + fn serialize_i8(self, v: i8) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + fn serialize_i16(self, v: i16) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + fn serialize_i32(self, v: i32) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + fn serialize_i64(self, v: i64) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + + fn serialize_u8(self, v: u8) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + fn serialize_u16(self, v: u16) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + fn serialize_u32(self, v: u32) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + fn serialize_u64(self, v: u64) -> Result { + Ok(KdlValue::Integer(v as i128)) + } + + fn serialize_f32(self, v: f32) -> Result { + Ok(KdlValue::Float(v as f64)) + } + fn serialize_f64(self, v: f64) -> Result { + Ok(KdlValue::Float(v)) + } + + fn serialize_char(self, v: char) -> Result { + Ok(KdlValue::String(v.to_string())) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(KdlValue::String(v.to_string())) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(ser::Error::custom( + "bytes cannot be directly represented as a KDL value", + )) + } + + fn serialize_none(self) -> Result { + Ok(KdlValue::Null) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(KdlValue::Null) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(KdlValue::Null) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + Ok(KdlValue::String(variant.to_string())) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result { + Err(ser::Error::custom( + "newtype variants cannot be represented as a single KDL value", + )) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(ser::Error::custom( + "sequences cannot be represented as a single KDL value", + )) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(ser::Error::custom( + "tuples cannot be represented as a single KDL value", + )) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "tuple structs cannot be represented as a single KDL value", + )) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "tuple variants cannot be represented as a single KDL value", + )) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(ser::Error::custom( + "maps cannot be represented as a single KDL value", + )) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "structs cannot be represented as a single KDL value", + )) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(ser::Error::custom( + "struct variants cannot be represented as a single KDL value", + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::Serialize; + + #[test] + fn simple_struct() { + #[derive(Serialize)] + struct Config { + name: String, + port: u16, + } + + let config = Config { + name: "my-app".into(), + port: 8080, + }; + let kdl = to_string(&config).unwrap(); + assert_eq!(kdl, "name \"my-app\"\nport 8080\n"); + } + + #[test] + fn nested_struct() { + #[derive(Serialize)] + struct Config { + server: Server, + } + + #[derive(Serialize)] + struct Server { + host: String, + port: u16, + } + + let config = Config { + server: Server { + host: "localhost".into(), + port: 8080, + }, + }; + let kdl = to_string(&config).unwrap(); + + assert!(kdl.contains("server")); + assert!(kdl.contains("host \"localhost\"")); + assert!(kdl.contains("port 8080")); + } + + #[test] + fn boolean_and_null() { + #[derive(Serialize)] + struct Config { + enabled: bool, + nothing: Option, + } + + let config = Config { + enabled: true, + nothing: None, + }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("enabled #true")); + assert!(kdl.contains("nothing #null")); + } + + #[test] + fn option_some() { + #[derive(Serialize)] + struct Config { + name: Option, + } + + let config = Config { + name: Some("hello".into()), + }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("name \"hello\"")); + } + + #[test] + fn unit_enum() { + #[derive(Serialize)] + enum Color { + Red, + } + + #[derive(Serialize)] + struct Config { + color: Color, + } + + let config = Config { color: Color::Red }; + let kdl = to_string(&config).unwrap(); + dbg!(&kdl); + assert!(kdl.contains("color \"Red\"")); + } + + #[test] + fn float_value() { + #[derive(Serialize)] + struct Config { + ratio: f64, + } + + let config = Config { ratio: 3.14 }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("ratio")); + assert!(kdl.contains("3.14")); + } + + #[test] + fn hashmap() { + use std::collections::BTreeMap; + + let mut map = BTreeMap::new(); + map.insert("alpha".to_string(), 1); + map.insert("beta".to_string(), 2); + let kdl = to_string(&map).unwrap(); + assert!(kdl.contains("alpha 1")); + assert!(kdl.contains("beta 2")); + } + + #[test] + fn newtype_struct() { + #[derive(Serialize)] + struct Port(u16); + + #[derive(Serialize)] + struct Config { + port: Port, + } + + let config = Config { port: Port(8080) }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("port 8080")); + } + + #[test] + fn to_document_roundtrip() { + #[derive(Serialize)] + struct Config { + name: String, + port: u16, + } + + let config = Config { + name: "test".into(), + port: 3000, + }; + let doc = to_document(&config).unwrap(); + assert_eq!(doc.nodes().len(), 2); + assert_eq!(doc.get("name").unwrap().get(0), Some(&"test".into())); + assert_eq!(doc.get("port").unwrap().get(0), Some(&3000.into())); + } +} From 6f5f7943b4729527e56fb5e17515d3ce97c9d0f2 Mon Sep 17 00:00:00 2001 From: Horu <73709188+HigherOrderLogic@users.noreply.github.com> Date: Tue, 19 May 2026 17:43:04 +0000 Subject: [PATCH 3/6] feat(serde): allow defining data shape (#157) --- src/de.rs | 201 +++++++++++++++++++++++++++++--- src/se.rs | 334 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 513 insertions(+), 22 deletions(-) diff --git a/src/de.rs b/src/de.rs index b8fc307..99934bb 100644 --- a/src/de.rs +++ b/src/de.rs @@ -37,9 +37,11 @@ //! assert_eq!(config, Config { name: "my-app".into(), port: 8080 }); //! ``` +use std::borrow::Cow; use std::fmt; -use serde::de::{self, DeserializeSeed, Deserializer as _, MapAccess, SeqAccess, Visitor}; +use serde::de::value::StringDeserializer; +use serde::de::{self, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::Deserialize; use crate::{KdlDocument, KdlEntry, KdlNode, KdlValue}; @@ -691,22 +693,21 @@ impl<'de, 'a> SeqAccess<'de> for NodeGroupSeqAccess<'a> { struct NodeMapAccess<'a> { /// Properties and children combined as (key, value_source). - entries: Vec<(&'a str, NodeMapValue<'a>)>, + entries: Vec<(Cow<'a, str>, NodeMapValue<'a>)>, idx: usize, } enum NodeMapValue<'a> { Arg(&'a KdlValue), + Args(Vec<&'a KdlEntry>), SingleNode(&'a KdlNode), MultiNode(Vec<&'a KdlNode>), } impl<'a> NodeMapAccess<'a> { fn new(node: &'a KdlNode) -> Self { - let mut entries: Vec<(&'a str, NodeMapValue<'a>)> = Vec::new(); + let mut entries: Vec<(Cow<'a, str>, NodeMapValue<'a>)> = Vec::new(); - // Add the first argument as "-" if there are args and also props/children - // (for mixed nodes). Otherwise arguments are not included in map mode. let args: Vec<_> = node .entries() .iter() @@ -718,18 +719,21 @@ impl<'a> NodeMapAccess<'a> { .filter(|e| e.name().is_some()) .collect(); - // If there are only args and no props/children, we shouldn't be in map mode. - // But if we are (struct requested), put args as "-" entries. - if !args.is_empty() && (props.is_empty() && node.children().is_none()) { - for arg in args.iter() { - entries.push(("-", NodeMapValue::Arg(arg.value()))); - } + for (i, arg) in args.iter().enumerate() { + entries.push(( + format!("$argument{}", i + 1).into(), + NodeMapValue::Arg(arg.value()), + )); + } + + if !args.is_empty() { + entries.push(("$arguments".into(), NodeMapValue::Args(args.clone()))); } - // Add properties for prop in &props { let name = prop.name().unwrap().value(); - entries.push((name, NodeMapValue::Arg(prop.value()))); + entries.push((format!("@{}", name).into(), NodeMapValue::Arg(prop.value()))); + entries.push((name.into(), NodeMapValue::Arg(prop.value()))); } // Add children (grouped by node name) @@ -747,9 +751,9 @@ impl<'a> NodeMapAccess<'a> { for key in child_keys { let group = child_groups.remove(key).unwrap(); if group.len() == 1 { - entries.push((key, NodeMapValue::SingleNode(group[0]))); + entries.push((key.into(), NodeMapValue::SingleNode(group[0]))); } else { - entries.push((key, NodeMapValue::MultiNode(group))); + entries.push((key.into(), NodeMapValue::MultiNode(group))); } } } @@ -768,8 +772,11 @@ impl<'de, 'a> MapAccess<'de> for NodeMapAccess<'a> { if self.idx >= self.entries.len() { return Ok(None); } - let key = self.entries[self.idx].0; - seed.deserialize(str_deserializer(key)).map(Some) + match &self.entries[self.idx].0 { + Cow::Borrowed(s) => seed.deserialize(str_deserializer(s)), + Cow::Owned(s) => seed.deserialize(StringDeserializer::new(s.clone())), + } + .map(Some) } fn next_value_seed>( @@ -780,12 +787,57 @@ impl<'de, 'a> MapAccess<'de> for NodeMapAccess<'a> { self.idx += 1; match value { NodeMapValue::Arg(v) => seed.deserialize(ValueDeserializer { value: v }), + NodeMapValue::Args(entries) => seed.deserialize(ArgsSeqDeserializer { + entries: entries.clone(), + }), NodeMapValue::SingleNode(node) => seed.deserialize(NodeDeserializer { node }), NodeMapValue::MultiNode(nodes) => seed.deserialize(NodeGroupDeserializer { nodes }), } } } +struct ArgsSeqDeserializer<'a> { + entries: Vec<&'a KdlEntry>, +} + +impl<'de, 'a> Deserializer<'de> for ArgsSeqDeserializer<'a> { + type Error = Error; + + fn deserialize_any>(self, visitor: V) -> Result { + visitor.visit_seq(ArgSeqAccess { + iter: self.entries.into_iter(), + }) + } + + fn deserialize_seq>(self, visitor: V) -> Result { + visitor.visit_seq(ArgSeqAccess { + iter: self.entries.into_iter(), + }) + } + + fn deserialize_option>(self, visitor: V) -> Result { + if self.entries.is_empty() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_newtype_struct>( + self, + _: &'static str, + visitor: V, + ) -> Result { + visitor.visit_newtype_struct(self) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string + bytes byte_buf unit unit_struct tuple + tuple_struct map struct enum identifier ignored_any + } +} + struct NodeEnumAccess<'a> { node: &'a KdlNode, } @@ -1289,4 +1341,119 @@ h 8 } ); } + + #[test] + fn rename_props() { + #[derive(Deserialize, Debug, PartialEq)] + struct Server { + #[serde(rename = "@host")] + host: String, + #[serde(rename = "@port")] + port: u16, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + server: Server, + } + + let kdl = r#"server host="localhost" port=8080"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + server: Server { + host: "localhost".into(), + port: 8080, + }, + } + ); + } + + #[test] + fn rename_args() { + #[derive(Deserialize, Debug, PartialEq)] + struct Server { + #[serde(rename = "$argument1")] + host: String, + #[serde(rename = "@port")] + port: u16, + #[serde(rename = "$arguments")] + all: Vec, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + server: Server, + } + + let kdl = r#"server "localhost" port=8080 "remote""#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + server: Server { + host: "localhost".into(), + port: 8080, + all: Vec::from(["localhost".into(), "remote".into()]) + }, + } + ); + } + + #[test] + fn rename_all_args() { + #[derive(Deserialize, Debug, PartialEq)] + struct Command { + #[serde(rename = "@name")] + name: String, + #[serde(rename = "$arguments")] + args: Vec, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + command: Command, + } + + let kdl = r#"command name="run" "--verbose" "--output" "result.txt""#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + command: Command { + name: "run".into(), + args: vec!["--verbose".into(), "--output".into(), "result.txt".into(),], + }, + } + ); + } + + #[test] + fn rename_children_args() { + #[derive(Deserialize, Debug, PartialEq)] + struct Server { + #[serde(rename = "@host")] + host: String, + #[serde(rename = "$arguments")] + ports: Vec, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct Config { + server: Server, + } + + let kdl = r#"server host="localhost" 8080 8081 8082"#; + let config: Config = from_str(kdl).unwrap(); + assert_eq!( + config, + Config { + server: Server { + host: "localhost".into(), + ports: vec![8080, 8081, 8082], + }, + } + ); + } } diff --git a/src/se.rs b/src/se.rs index a08e60b..d16bf14 100644 --- a/src/se.rs +++ b/src/se.rs @@ -761,11 +761,24 @@ impl<'a> ser::SerializeStruct for NodeChildMapSerializer<'a> { key: &'static str, value: &T, ) -> Result<(), Self::Error> { - let mut child = KdlNode::new(key); - let mut child_ser = NodeValueSerializer { node: &mut child }; - value.serialize(&mut child_ser)?; - let children = self.node.ensure_children(); - children.nodes_mut().push(child); + if let Some(attr_name) = key.strip_prefix('@') { + let kdl_val = to_kdl_value(value)?; + self.node + .entries_mut() + .push(KdlEntry::new_prop(attr_name, kdl_val)); + } else if key == "$arguments" { + let mut ser = ArgsSerializer { node: self.node }; + value.serialize(&mut ser)?; + } else if key.starts_with("$argument") { + let kdl_val = to_kdl_value(value)?; + self.node.entries_mut().push(KdlEntry::new(kdl_val)); + } else { + let mut child = KdlNode::new(key); + let mut child_ser = NodeValueSerializer { node: &mut child }; + value.serialize(&mut child_ser)?; + let children = self.node.ensure_children(); + children.nodes_mut().push(child); + } Ok(()) } @@ -1170,6 +1183,235 @@ impl ser::Serializer for KdlValueSerializer { } } +struct ArgsSerializer<'a> { + node: &'a mut KdlNode, +} + +impl<'a> ser::Serializer for &'a mut ArgsSerializer<'a> { + type Ok = (); + type Error = Error; + + type SerializeSeq = ArgsSeqSerializer<'a>; + type SerializeTuple = ArgsSeqSerializer<'a>; + type SerializeTupleStruct = ArgsSeqSerializer<'a>; + type SerializeTupleVariant = ser::Impossible<(), Error>; + type SerializeMap = ser::Impossible<(), Error>; + type SerializeStruct = ser::Impossible<(), Error>; + type SerializeStructVariant = ser::Impossible<(), Error>; + + fn serialize_seq(self, _: Option) -> Result { + Ok(ArgsSeqSerializer { node: self.node }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_bool(self, v: bool) -> Result { + self.node.entries_mut().push(KdlEntry::new(v)); + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(v as i64) + } + fn serialize_i64(self, v: i64) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::Integer(v as i128))); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u16(self, v: u16) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u32(self, v: u32) -> Result { + self.serialize_u64(v as u64) + } + fn serialize_u64(self, v: u64) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::Integer(v as i128))); + Ok(()) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(v as f64) + } + fn serialize_f64(self, v: f64) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::Float(v))); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + self.node + .entries_mut() + .push(KdlEntry::new(KdlValue::String(v.to_string()))); + Ok(()) + } + + fn serialize_bytes(self, _: &[u8]) -> Result { + Err(ser::Error::custom( + "bytes cannot be represented as KDL arguments", + )) + } + + fn serialize_none(self) -> Result { + self.node.entries_mut().push(KdlEntry::new(KdlValue::Null)); + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + self.node.entries_mut().push(KdlEntry::new(KdlValue::Null)); + Ok(()) + } + + fn serialize_unit_struct(self, _: &'static str) -> Result { + self.serialize_unit() + } + + fn serialize_unit_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct( + self, + _: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: &T, + ) -> Result { + Err(ser::Error::custom( + "newtype variants cannot be represented as KDL arguments", + )) + } + + fn serialize_map(self, _: Option) -> Result { + Err(ser::Error::custom( + "maps are cannot be represented as KDL arguments", + )) + } + + fn serialize_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Err(ser::Error::custom( + "structs are cannot be represented as KDL arguments", + )) + } + + fn serialize_struct_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + Err(ser::Error::custom( + "struct variants cannot be represented as KDL arguments", + )) + } + + fn serialize_tuple_variant( + self, + _: &'static str, + _: u32, + _: &'static str, + _: usize, + ) -> Result { + Err(ser::Error::custom( + "tuple variants cannot be represented as KDL arguments", + )) + } +} + +struct ArgsSeqSerializer<'a> { + node: &'a mut KdlNode, +} + +impl<'a> ser::SerializeSeq for ArgsSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + let kdl_val = to_kdl_value(value)?; + self.node.entries_mut().push(KdlEntry::new(kdl_val)); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::SerializeTuple for ArgsSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + +impl<'a> ser::SerializeTupleStruct for ArgsSeqSerializer<'a> { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + ser::SerializeSeq::end(self) + } +} + #[cfg(test)] mod tests { use super::*; @@ -1323,4 +1565,86 @@ mod tests { assert_eq!(doc.get("name").unwrap().get(0), Some(&"test".into())); assert_eq!(doc.get("port").unwrap().get(0), Some(&3000.into())); } + + #[test] + fn rename_props() { + #[derive(Serialize)] + struct Server { + #[serde(rename = "@host")] + host: String, + #[serde(rename = "@port")] + port: u16, + } + + #[derive(Serialize)] + struct Config { + server: Server, + } + + let config = Config { + server: Server { + host: "localhost".into(), + port: 8080, + }, + }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("server")); + assert!(kdl.contains("host=localhost")); + assert!(kdl.contains("port=8080")); + } + + #[test] + fn rename_args() { + #[derive(Serialize)] + struct Server { + #[serde(rename = "$argument1")] + host: String, + #[serde(rename = "@port")] + port: u16, + } + + #[derive(Serialize)] + struct Config { + server: Server, + } + + let config = Config { + server: Server { + host: "localhost".into(), + port: 8080, + }, + }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("server")); + assert!(kdl.contains("localhost")); + assert!(kdl.contains("port=8080")); + } + + #[test] + fn rename_all_args() { + #[derive(Serialize)] + struct Command { + #[serde(rename = "@name")] + name: String, + #[serde(rename = "$arguments")] + args: Vec, + } + + #[derive(Serialize)] + struct Config { + command: Command, + } + + let config = Config { + command: Command { + name: "run".into(), + args: vec!["--verbose".into(), "--output".into()], + }, + }; + let kdl = to_string(&config).unwrap(); + assert!(kdl.contains("command")); + assert!(kdl.contains("name=run")); + assert!(kdl.contains("--verbose")); + assert!(kdl.contains("--output")); + } } From 68af7756e93435989f6ebf53bb83356ee33eb095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Tue, 19 May 2026 10:51:42 -0700 Subject: [PATCH 4/6] Update de.rs --- src/de.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/de.rs b/src/de.rs index 99934bb..26828f3 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1357,7 +1357,7 @@ h 8 server: Server, } - let kdl = r#"server host="localhost" port=8080"#; + let kdl = "server host=localhost port=8080"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, @@ -1387,7 +1387,7 @@ h 8 server: Server, } - let kdl = r#"server "localhost" port=8080 "remote""#; + let kdl = "server localhost port=8080 remote"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, @@ -1416,7 +1416,7 @@ h 8 command: Command, } - let kdl = r#"command name="run" "--verbose" "--output" "result.txt""#; + let kdl = "command name=run --verbose --output result.txt"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, @@ -1444,7 +1444,7 @@ h 8 server: Server, } - let kdl = r#"server host="localhost" 8080 8081 8082"#; + let kdl = "server host=localhost 8080 8081 8082"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, From e9df058c25cd4486df8fe568d2ff24ea2c4ed0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Tue, 19 May 2026 13:44:51 -0700 Subject: [PATCH 5/6] clippy fix --- Cargo.lock | 1 + src/v2_parser.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3c4944..83cf19b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,6 +399,7 @@ dependencies = [ "miette 7.6.0", "num", "pretty_assertions", + "serde", "thiserror 2.0.17", "winnow", ] diff --git a/src/v2_parser.rs b/src/v2_parser.rs index 395b186..83560d6 100644 --- a/src/v2_parser.rs +++ b/src/v2_parser.rs @@ -380,12 +380,12 @@ fn base_node(input: &mut Input<'_>) -> PResult { // _both_ the error message for a string/ident parser error _and_ the error // message for a node name being expected. if !name_is_valid { - resume_after_cut(|input: &mut Input<'_>| -> PResult<()> { + resume_after_cut((|input: &mut Input<'_>| -> PResult<()> { Err(ErrMode::Cut(KdlParseError { span: Some(span_from_checkpoint(input, &_before_ident)), ..Default::default() })) - }.context(cx().msg("Found invalid node name") + }).context(cx().msg("Found invalid node name") .lbl("node name") .hlp("This can be any string type, including a quoted, raw, or multiline string, as well as a plain identifier string.")), empty).parse_next(input)?; From 75d4fa039a804ca9238752f796d5379bcc9f7f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Thu, 28 May 2026 12:52:40 -0700 Subject: [PATCH 6/6] bump msrv --- .github/workflows/ci.yml | 2 +- Cargo.toml | 2 +- clippy.toml | 2 +- src/lib.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02ba346..4ad175e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - rust: [1.82, stable] + rust: [1.96, stable] os: [ubuntu-latest, macOS-latest, windows-latest] steps: diff --git a/Cargo.toml b/Cargo.toml index a6071d2..36be272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" homepage = "https://kdl.dev" repository = "https://github.com/kdl-org/kdl-rs" keywords = ["kdl", "document", "serialization", "config"] -rust-version = "1.82" +rust-version = "1.96" edition = "2021" [features] diff --git a/clippy.toml b/clippy.toml index 4579197..5bb56f2 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.82" +msrv = "1.96" diff --git a/src/lib.rs b/src/lib.rs index a8c021b..2d487b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,7 +140,7 @@ //! //! ## Minimum Supported Rust Version (MSRV) //! -//! You must be at least `1.82` tall to get on this ride. +//! You must be at least `1.96` tall to get on this ride. //! //! ## License //!