//! Serde deserializer for KDL documents. //! //! This module provides [`from_str`] for deserializing Rust types from KDL text. //! //! Due to the fact that `serde` was developed with JSON in mind, and the incompatibility //! between KDL and JSON's data model, not all `serde` concepts apply smoothly to KDL. This //! leads to the fact that some KDL concepts are inexpressible in terms of `serde` derives //! and may require manual deserialization. //! //! The most notable restriction is the ability to distinguish between *arguments*, //! *properties* and *child nodes*, as JSON does not have such conception. //! //! Due to that the mapping is performed in a best effort manner. //! //! # 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 no arguments** and no properties/children is treated as //! a **boolean flag**. //! - 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, //! active: bool //! } //! //! let kdl = r#" //! name my-app //! port 8080 //! active //! "#; //! //! let config: Config = kdl::de::from_str(kdl).unwrap(); //! assert_eq!(config, Config { name: "my-app".into(), port: 8080, active: true }); //! ``` //! //! ## Arguments mapping //! //! This library supports three kinds of special renaming for arguments - `#{n}`, `#args`, and `#rest`: //! //! - `#{n}`: This is used when you want to access argument at a specific //! position. The field name should start with `#`, then follow by the position //! of the argument in numerical order starting from 0. //! //! ```rust //! use serde::Deserialize; //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Config { //! server: Server, //! } //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Server { //! #[serde(rename = "#0")] //! name: String, //! #[serde(rename = "#1")] //! port: u16, //! } //! //! let kdl = r#" //! server my-app 8080 //! "#; //! //! let config: Config = kdl::de::from_str(kdl).unwrap(); //! assert_eq!(config, Config { server: Server { name: "my-app".into(), port: 8080 }}); //! ``` //! //! - `#args`: This is used when you want to collect *all* the arguments of the node. //! //! ```rust //! use serde::Deserialize; //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Config { //! server: Server, //! } //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Server { //! #[serde(rename = "#args")] //! info: Vec, //! } //! //! let kdl = r#" //! server my-app "https://example.com" //! "#; //! //! let config: Config = kdl::de::from_str(kdl).unwrap(); //! assert_eq!(config, Config { server: Server { info: Vec::from(["my-app".into(), "https://example.com".into()]) }}); //! ``` //! //! - `#rest`: This is used when you want to collect everything that wasn't //! referenced by an `#{n}` rename. //! //! ```rust //! use serde::Deserialize; //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Config { //! command: Command, //! } //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Command { //! #[serde(rename = "#0")] //! cmd: String, //! #[serde(rename = "#rest")] //! args: Vec, //! } //! //! let kdl = r#" //! command frob "https://example.com" x y //! "#; //! //! let config: Config = kdl::de::from_str(kdl).unwrap(); //! assert_eq!(config, Config { command: Command { cmd: "frob".into(), args: vec!["https://example.com".into(), "x".into(), "y".into()]}}); //! ``` //! //! ## Properties mapping //! //! You can use `#@field-name` on a field that will be used for a property. //! //! ```rust //! use serde::Deserialize; //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Config { //! server: Server, //! } //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Server { //! #[serde(rename = "#0")] //! name: String, //! #[serde(rename = "#@port")] //! port: u16, //! } //! //! let kdl = r#" //! server my-app port=8080 //! "#; //! //! let config: Config = kdl::de::from_str(kdl).unwrap(); //! assert_eq!(config, Config { server: Server { name: "my-app".into(), port: 8080 }}); //! ``` //! //! ## Types mapping //! //! You can extract type annotations for nodes, arguments and properties by //! using `#type`: //! //! ```rust //! use serde::Deserialize; //! //! #[derive(Deserialize, Debug, PartialEq)] //! struct Config { //! server: Server, //! } //! //! #[derive(Deserialize, Debug, PartialEq)] //! #[serde(rename_all = "kebab-case")] //! enum ServerKind { //! Big, //! Small, //! } //! #[derive(Deserialize, Debug, PartialEq)] //! struct Server { //! #[serde(rename = "#type")] //! server_kind: ServerKind, //! #[serde(rename = "#0#type")] //! app_type: String, //! #[serde(rename = "#@port#type")] //! port_type: String, //! } //! //! let kdl = r#" //! (big)server (cool)my-app port=(internal)8080 //! "#; //! //! let config: Config = kdl::de::from_str(kdl).unwrap(); //! assert_eq!(config, Config { server: Server { server_kind: ServerKind::Big, app_type: "cool".into(), port_type: "internal".into() }}); //! ``` use std::borrow::Cow; use std::sync::Arc; use std::{fmt, iter}; use miette::{Diagnostic, LabeledSpan, Severity, SourceCode, SourceSpan}; use serde::Deserialize; use serde::de::value::StringDeserializer; use serde::de::{self, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor}; use crate::{KdlDiagnostic, KdlDocument, KdlEntry, KdlIdentifier, KdlNode, KdlValue}; /// Errors that can occur during KDL deserialization. #[derive(Debug, Clone)] pub struct Error { msg: String, input: Option>, span: Option, label: Option, diagnostics: Vec, } impl Error { fn new(msg: impl fmt::Display) -> Self { Error { msg: msg.to_string(), input: None, span: None, label: None, diagnostics: Vec::new(), } } /// Gets the source span associated with this deserialization error. pub fn span(&self) -> Option { self.span } /// Gets this error as a KDL diagnostic, if available. pub fn diagnostic(&self) -> Option { if let Some(diagnostic) = self.diagnostics.first() { Some(diagnostic.clone()) } else { Some(KdlDiagnostic { input: self.input.clone()?, span: self.span?, message: Some(self.msg.clone()), label: self.label.clone(), help: None, severity: Severity::Error, }) } } fn with_input(mut self, input: &Arc) -> Self { if self.input.is_none() { self.input = Some(input.clone()); } self } fn with_span( mut self, input: &Arc, span: SourceSpan, label: impl Into, ) -> Self { if self.input.is_none() { self.input = Some(input.clone()); } if self.span.is_none() { self.span = Some(span); self.label = Some(label.into()); } self } } 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 Diagnostic for Error { fn source_code(&self) -> Option<&dyn SourceCode> { self.input.as_ref().map(|input| input as &dyn SourceCode) } fn severity(&self) -> Option { Some(Severity::Error) } fn labels(&self) -> Option + '_>> { let span = self.span?; let label = self.label.clone(); Some(Box::new(iter::once(LabeledSpan::new_with_span( label, span, )))) } fn related<'a>(&'a self) -> Option + 'a>> { if self.diagnostics.is_empty() { None } else { Some(Box::new( self.diagnostics.iter().map(|d| d as &dyn Diagnostic), )) } } } impl de::Error for Error { fn custom(msg: T) -> Self { Error::new(msg) } } impl From for Error { fn from(e: crate::KdlError) -> Self { Error { msg: e.to_string(), input: Some(e.input.clone()), span: e.diagnostics.first().map(|diagnostic| diagnostic.span), label: e .diagnostics .first() .and_then(|diagnostic| diagnostic.label.clone()), diagnostics: e.diagnostics, } } } /// Helper to produce a `StrDeserializer` with our error type. fn str_deserializer(s: &str) -> de::value::StrDeserializer<'_, Error> { de::IntoDeserializer::into_deserializer(s) } fn entry_span(entry: &KdlEntry) -> SourceSpan { #[cfg(feature = "span")] { entry.span() } #[cfg(not(feature = "span"))] { SourceSpan::from(0..0) } } fn ident_span(ident: &KdlIdentifier) -> SourceSpan { #[cfg(feature = "span")] { ident.span() } #[cfg(not(feature = "span"))] { SourceSpan::from(0..0) } } fn node_span(node: &KdlNode) -> SourceSpan { #[cfg(feature = "span")] { node.span() } #[cfg(not(feature = "span"))] { SourceSpan::from(0..0) } } /// 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 = input.parse()?; let input = input.to_owned().into(); let de = DocumentDeserializer { doc: &doc, input: &input, }; T::deserialize(de).map_err(|err| err.with_input(&input)) } struct IdentDeserializer<'a> { ident: &'a KdlIdentifier, input: &'a Arc, span: SourceSpan, } impl<'de, 'a> de::Deserializer<'de> for IdentDeserializer<'a> { type Error = Error; fn deserialize_any>(self, visitor: V) -> Result { self.annotate(visitor.visit_str(self.ident.value())) } fn deserialize_string>(self, visitor: V) -> Result { self.annotate(visitor.visit_string(self.ident.value().into())) } fn deserialize_str>(self, visitor: V) -> Result { self.annotate(visitor.visit_str(self.ident.value())) } 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 { self.annotate(visitor.visit_enum(str_deserializer(self.ident.value()))) } 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 option identifier ignored_any } } impl<'a> IdentDeserializer<'a> { fn new(ident: &'a KdlIdentifier, input: &'a Arc) -> Self { IdentDeserializer { ident, input, span: ident_span(ident), } } fn annotate(&self, result: Result) -> Result { result.map_err(|err| { err.with_span( self.input, self.span, "while deserializing this KDL identifier", ) }) } } struct ValueDeserializer<'a> { value: &'a KdlValue, input: &'a Arc, span: SourceSpan, } impl<'a> ValueDeserializer<'a> { fn new(entry: &'a KdlEntry, input: &'a Arc) -> Self { ValueDeserializer { value: entry.value(), input, span: entry_span(entry), } } fn annotate(&self, result: Result) -> Result { result.map_err(|err| { err.with_span(self.input, self.span, "while deserializing this KDL value") }) } } impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> { type Error = Error; fn deserialize_any>(self, visitor: V) -> Result { match self.value { KdlValue::String(s) => self.annotate(visitor.visit_str(s)), KdlValue::Integer(n) => { if let Ok(i) = i64::try_from(*n) { self.annotate(visitor.visit_i64(i)) } else if let Ok(u) = u64::try_from(*n) { self.annotate(visitor.visit_u64(u)) } else { self.annotate(visitor.visit_i128(*n)) } } KdlValue::Float(f) => self.annotate(visitor.visit_f64(*f)), KdlValue::Bool(b) => self.annotate(visitor.visit_bool(*b)), KdlValue::Null => self.annotate(visitor.visit_unit()), } } fn deserialize_option>(self, visitor: V) -> Result { match self.value { KdlValue::Null => self.annotate(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 { self.annotate(match self.value { KdlValue::String(s) => visitor.visit_enum(str_deserializer(s.as_str())), _ => Err(Error::new("expected a string for unit enum variant")), }) } fn deserialize_string>(self, visitor: V) -> Result { match self.value { KdlValue::String(s) => self.annotate(visitor.visit_string(s.clone())), _ => self.deserialize_any(visitor), } } fn deserialize_str>(self, visitor: V) -> Result { match self.value { KdlValue::String(s) => self.annotate(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, input: &'a Arc, } 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, self.input)) } 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(), input: self.input, }) } 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], input: self.input, }) } else { Err(Error::new( "expected exactly one node for enum deserialization", )) .map_err(|err| err.with_input(self.input)) } } 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, /// The input string. input: &'a Arc, } impl<'a> DocumentMapAccess<'a> { fn new(doc: &'a KdlDocument, input: &'a Arc) -> 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, input, } } } 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], input: self.input, }) } else { // Multiple nodes with same name → sequence seed.deserialize(NodeGroupDeserializer { nodes, input: self.input, }) } } } struct NodeDeserializer<'a> { node: &'a KdlNode, input: &'a Arc, } 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 → bool flag return visitor.visit_bool(true); } if self.is_scalar() { // Single argument → deserialize as scalar let entry = self.args()[0]; return ValueDeserializer::new(entry, self.input).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(), input: self.input, }); } // Has properties and/or children → map self.deserialize_map(visitor) } fn deserialize_bool>(self, visitor: V) -> Result { if self.is_scalar() { ValueDeserializer::new(self.args()[0], self.input).deserialize_any(visitor) } else if self.is_empty() { visitor.visit_bool(true) } else { Err(Error::new("expected a boolean value")).map_err(|err| { err.with_span( self.input, node_span(self.node), "while deserializing this KDL node", ) }) } } 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(), input: self.input, }) } else if let Some(children) = self.node.children() { // Children → sequence of nodes visitor.visit_seq(NodeListSeqAccess { iter: children.nodes().iter(), input: self.input, }) } else { // Empty → empty sequence visitor.visit_seq(ArgSeqAccess { iter: Vec::new().into_iter(), input: self.input, }) } } 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, self.input, None)) } fn deserialize_struct>( self, _name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result { visitor.visit_map(NodeMapAccess::new(self.node, self.input, Some(fields))) } 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() && let KdlValue::String(s) = self.args()[0].value() { return visitor .visit_enum(str_deserializer(s.as_str())) .map_err(|err| { err.with_span( self.input, entry_span(self.args()[0]), "while deserializing this enum variant", ) }); } // 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], input: self.input, }); } } // 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, entry: prop, input: self.input, }); } // Fall back: just node name is variant visitor.visit_enum(NodeEnumAccess { node: self.node, input: self.input, }) } 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::new(self.args()[0], self.input).deserialize_any(visitor) } else { self.deserialize_any(visitor) } } } struct ArgSeqAccess<'a> { iter: std::vec::IntoIter<&'a KdlEntry>, input: &'a Arc, } impl<'de, 'a> SeqAccess<'de> for ArgSeqAccess<'a> { type Error = Error; fn next_element_seed>( &mut self, seed: T, ) -> Result, Self::Error> { self.iter .next() .map(|e| seed.deserialize(ValueDeserializer::new(e, self.input))) .transpose() } } struct NodeListSeqAccess<'a> { iter: std::slice::Iter<'a, KdlNode>, input: &'a Arc, } impl<'de, 'a> SeqAccess<'de> for NodeListSeqAccess<'a> { type Error = Error; fn next_element_seed>( &mut self, seed: T, ) -> Result, Self::Error> { self.iter .next() .map(|node| { seed.deserialize(NodeDeserializer { node, input: self.input, }) }) .transpose() } } struct NodeGroupDeserializer<'a> { nodes: &'a [&'a KdlNode], input: &'a Arc, } 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(), input: self.input, }) } 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>, input: &'a Arc, } impl<'de, 'a> SeqAccess<'de> for NodeGroupSeqAccess<'a> { type Error = Error; fn next_element_seed>( &mut self, seed: T, ) -> Result, Self::Error> { self.iter .next() .map(|node| { seed.deserialize(NodeDeserializer { node, input: self.input, }) }) .transpose() } } #[derive(Debug)] struct NodeMapAccess<'a> { /// Properties and children combined as (key, value_source). entries: Vec<(Cow<'a, str>, NodeMapValue<'a>)>, idx: usize, input: &'a Arc, } #[derive(Debug)] enum NodeMapValue<'a> { Ident(&'a KdlIdentifier), Arg(&'a KdlEntry), Args(Vec<&'a KdlEntry>), SingleNode(&'a KdlNode), MultiNode(Vec<&'a KdlNode>), Omitted, } impl<'a> NodeMapAccess<'a> { fn new( node: &'a KdlNode, input: &'a Arc, fields: Option<&'static [&'static str]>, ) -> Self { let mut entries: Vec<(Cow<'a, str>, NodeMapValue<'a>)> = Vec::new(); if let Some(fields) = fields { if fields.contains(&"#name") { entries.push(("#name".into(), NodeMapValue::Ident(node.name()))); } if fields.contains(&"#type") && let Some(ty) = node.ty() { entries.push(("#type".into(), NodeMapValue::Ident(ty))); } } if let Some(fields) = fields && let collect_all = fields.contains(&"#args") && let collect_rest = fields.contains(&"#rest") { let mut args = Vec::new(); let mut rest = Vec::new(); for (i, arg) in node .entries() .iter() .filter(|e| e.name().is_none()) .enumerate() { if collect_all { args.push(arg); } let idx_name = format!("#{i}"); let ty_name = format!("{idx_name}#type"); if let Some(ty) = arg.ty() { entries.push((ty_name.into(), NodeMapValue::Ident(ty))); } if fields.contains(&idx_name.as_ref()) { entries.push((idx_name.into(), NodeMapValue::Arg(arg))); } else if collect_rest { rest.push(arg); } } if collect_all { entries.push(("#args".into(), NodeMapValue::Args(args))); } if collect_rest { entries.push(("#rest".into(), NodeMapValue::Args(rest))); } } let mut used_names = Vec::new(); for prop in node.entries().iter().filter(|e| e.name().is_some()) { // SAFETY: we just filtered for things with names. let name = prop.name().unwrap().value(); if let Some(fields) = fields { let hash_name = format!("#@{name}"); let ty_name = format!("{hash_name}#type"); if fields.contains(&hash_name.as_ref()) { entries.push((hash_name.into(), NodeMapValue::Arg(prop))); } if let Some(ty) = prop.ty() && fields.contains(&ty_name.as_ref()) { entries.push((ty_name.into(), NodeMapValue::Ident(ty))); } } entries.push((name.into(), NodeMapValue::Arg(prop))); used_names.push(name); } // 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(); used_names.push(name); if !child_groups.contains_key(name) { child_keys.push(name); } 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.into(), NodeMapValue::SingleNode(group[0]))); } else { entries.push((key.into(), NodeMapValue::MultiNode(group))); } } } if let Some(fields) = fields { for field in fields { if !field.starts_with('#') && !used_names.contains(field) { entries.push((String::from(*field).into(), NodeMapValue::Omitted)); } } } dbg!(&entries); NodeMapAccess { entries, idx: 0, input, } } } 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); } 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>( &mut self, seed: V, ) -> Result { let (_, ref value) = self.entries[self.idx]; self.idx += 1; match value { NodeMapValue::Ident(ident) => { seed.deserialize(IdentDeserializer::new(ident, self.input)) } NodeMapValue::Arg(entry) => seed.deserialize(ValueDeserializer::new(entry, self.input)), NodeMapValue::Args(entries) => seed.deserialize(ArgsSeqDeserializer { entries: entries.clone(), input: self.input, }), NodeMapValue::SingleNode(node) => seed.deserialize(NodeDeserializer { node, input: self.input, }), NodeMapValue::MultiNode(nodes) => seed.deserialize(NodeGroupDeserializer { nodes, input: self.input, }), NodeMapValue::Omitted => seed.deserialize(OmittedChildDeserializer), } } } struct ArgsSeqDeserializer<'a> { entries: Vec<&'a KdlEntry>, input: &'a Arc, } 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(), input: self.input, }) } fn deserialize_seq>(self, visitor: V) -> Result { visitor.visit_seq(ArgSeqAccess { iter: self.entries.into_iter(), input: self.input, }) } 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 OmittedChildDeserializer; impl<'de> Deserializer<'de> for OmittedChildDeserializer { type Error = Error; fn deserialize_any>(self, visitor: V) -> Result { visitor.visit_bool(false) } fn deserialize_bool(self, visitor: V) -> Result where V: Visitor<'de>, { visitor.visit_bool(false) } fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de>, { visitor.visit_none() } serde::forward_to_deserialize_any! { i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf unit unit_struct tuple newtype_struct seq tuple_struct map struct enum identifier ignored_any } } struct NodeEnumAccess<'a> { node: &'a KdlNode, input: &'a Arc, } 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)) .map_err(|err| { err.with_span( self.input, node_span(self.node), "while deserializing this enum variant", ) })?; Ok(( val, NodeVariantAccess { node: self.node, input: self.input, }, )) } } struct NodeVariantAccess<'a> { node: &'a KdlNode, input: &'a Arc, } 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, input: self.input, }) } fn tuple_variant>( self, _len: usize, visitor: V, ) -> Result { de::Deserializer::deserialize_seq( NodeDeserializer { node: self.node, input: self.input, }, visitor, ) } fn struct_variant>( self, _fields: &'static [&'static str], visitor: V, ) -> Result { de::Deserializer::deserialize_map( NodeDeserializer { node: self.node, input: self.input, }, visitor, ) } } struct PropertyEnumAccess<'a> { key: &'a str, entry: &'a KdlEntry, input: &'a Arc, } impl<'de, 'a> de::EnumAccess<'de> for PropertyEnumAccess<'a> { type Error = Error; type Variant = PropertyVariantAccess<'a>; fn variant_seed>( self, seed: V, ) -> Result<(V::Value, Self::Variant), Self::Error> { let val = seed .deserialize(str_deserializer(self.key)) .map_err(|err| { err.with_span( self.input, entry_span(self.entry), "while deserializing this enum variant", ) })?; Ok(( val, PropertyVariantAccess { entry: self.entry, input: self.input, }, )) } } struct PropertyVariantAccess<'a> { entry: &'a KdlEntry, input: &'a Arc, } 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::new(self.entry, self.input)) } fn tuple_variant>( self, _len: usize, _visitor: V, ) -> Result { Err(Error::new( "tuple variants not supported from KDL properties", )) .map_err(|err| { err.with_span( self.input, entry_span(self.entry), "while deserializing this KDL property", ) }) } fn struct_variant>( self, _fields: &'static [&'static str], _visitor: V, ) -> Result { Err(Error::new( "struct variants not supported from KDL properties", )) .map_err(|err| { err.with_span( self.input, entry_span(self.entry), "while deserializing this KDL property", ) }) } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "span")] use miette::SourceCode; 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, } ); } #[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 = "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)] #[serde(rename_all = "kebab-case")] enum HostType { IpAddr, Hostname, } #[derive(Deserialize, Debug, PartialEq)] struct Server { #[serde(rename = "#type")] server_type: String, #[serde(rename = "#0#type")] host_type: HostType, #[serde(rename = "#0")] host: String, #[serde(rename = "#@port#type")] port_type: String, #[serde(rename = "#@port")] port: u16, #[serde(rename = "#args")] all: Vec, } #[derive(Deserialize, Debug, PartialEq)] struct Config { server: Server, } let kdl = "(linux-debian-arm64)server (hostname)localhost port=(number)8080 remote"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, Config { server: Server { server_type: "linux-debian-arm64".into(), host_type: HostType::Hostname, host: "localhost".into(), port_type: "number".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 = "#args")] args: Vec, } #[derive(Deserialize, Debug, PartialEq)] struct Config { command: Command, } let kdl = "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_rest_args() { #[derive(Deserialize, Debug, PartialEq)] struct Command { #[serde(rename = "#0")] name: String, #[serde(rename = "#rest")] args: Vec, } #[derive(Deserialize, Debug, PartialEq)] struct Config { command: Command, } let kdl = "command 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 = "#args")] ports: Vec, } #[derive(Deserialize, Debug, PartialEq)] struct Config { server: Server, } let kdl = "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], }, } ); } #[test] fn bool_flag_children() { #[derive(Deserialize, Debug, PartialEq)] #[serde(rename_all = "kebab-case")] struct Server { host: String, active: bool, } #[derive(Deserialize, Debug, PartialEq)] struct Config { server: Server, } let kdl = "server host=localhost { active }"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, Config { server: Server { host: "localhost".into(), active: true, }, } ); let kdl = "server host=localhost active=#true"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, Config { server: Server { host: "localhost".into(), active: true, }, } ); let kdl = "server host=localhost"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, Config { server: Server { host: "localhost".into(), active: false, }, } ); let kdl = "server host=localhost { }"; let config: Config = from_str(kdl).unwrap(); assert_eq!( config, Config { server: Server { host: "localhost".into(), active: false, }, } ); } // TODO(@zkat): Can't figure out how to do internally tagged stuff just yet... // #[test] // fn internal_tag_rename() { // #[derive(Deserialize, Debug, PartialEq)] // #[serde(tag = "#0", rename_all = "kebab-case")] // enum Upstream { // Git { // #[serde(rename = "#1")] // url: String, // hash: Option, // }, // Svn { // #[serde(rename = "#1")] // url: String, // rev: Option, // }, // } // #[derive(Deserialize, Debug, PartialEq)] // struct Config { // #[serde(rename = "upstream")] // upstreams: Vec, // } // let kdl = r#"upstream git "https://codeberg.org/kdl/kdl-rs""#; // let config: Config = from_str(kdl).unwrap(); // assert_eq!( // config, // Config { // upstreams: vec![Upstream::Git { // url: "https://codeberg.org/kdl/kdl-rs".into(), // hash: None, // }] // } // ); // } #[test] fn error_span_and_diagnostic() { #[derive(Deserialize, Debug, PartialEq)] struct Config { port: u16, } let kdl = "port nope"; let err = from_str::(kdl).unwrap_err(); let labels: Vec<_> = err.labels().unwrap().collect(); assert_eq!( labels[0].label().unwrap(), "while deserializing this KDL value" ); let diagnostic = err.diagnostic().unwrap(); let diag_labels: Vec<_> = diagnostic.labels().unwrap().collect(); assert_eq!( diag_labels[0].label().unwrap(), "while deserializing this KDL value" ); #[cfg(feature = "span")] { let span = err.span().unwrap(); assert_eq!(span, diagnostic.span); assert_eq!(kdl.read_span(&span, 0, 0).unwrap().data(), b"nope",); assert_eq!( diagnostic .source_code() .unwrap() .read_span(labels[0].inner(), 0, 0) .unwrap() .data(), b"nope", ); } } }