kdl-rs/src/de.rs

2230 lines
61 KiB
Rust

//! 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<String>,
//! }
//!
//! 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<String>,
//! }
//!
//! 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<Arc<String>>,
span: Option<SourceSpan>,
label: Option<String>,
diagnostics: Vec<KdlDiagnostic>,
}
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<SourceSpan> {
self.span
}
/// Gets this error as a KDL diagnostic, if available.
pub fn diagnostic(&self) -> Option<KdlDiagnostic> {
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<String>) -> Self {
if self.input.is_none() {
self.input = Some(input.clone());
}
self
}
fn with_span(
mut self,
input: &Arc<String>,
span: SourceSpan,
label: impl Into<String>,
) -> 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<Severity> {
Some(Severity::Error)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
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<Box<dyn Iterator<Item = &'a dyn Diagnostic> + '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<T: fmt::Display>(msg: T) -> Self {
Error::new(msg)
}
}
impl From<crate::KdlError> 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<T, Error>
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<String>,
span: SourceSpan,
}
impl<'de, 'a> de::Deserializer<'de> for IdentDeserializer<'a> {
type Error = Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.annotate(visitor.visit_str(self.ident.value()))
}
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.annotate(visitor.visit_string(self.ident.value().into()))
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.annotate(visitor.visit_str(self.ident.value()))
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_newtype_struct(self)
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
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<String>) -> Self {
IdentDeserializer {
ident,
input,
span: ident_span(ident),
}
}
fn annotate<T>(&self, result: Result<T, Error>) -> Result<T, Error> {
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<String>,
span: SourceSpan,
}
impl<'a> ValueDeserializer<'a> {
fn new(entry: &'a KdlEntry, input: &'a Arc<String>) -> Self {
ValueDeserializer {
value: entry.value(),
input,
span: entry_span(entry),
}
}
fn annotate<T>(&self, result: Result<T, Error>) -> Result<T, Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
match self.value {
KdlValue::Null => self.annotate(visitor.visit_none()),
_ => visitor.visit_some(self),
}
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_newtype_struct(self)
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
match self.value {
KdlValue::String(s) => self.annotate(visitor.visit_string(s.clone())),
_ => self.deserialize_any(visitor),
}
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<String>,
}
impl<'de, 'a> de::Deserializer<'de> for DocumentDeserializer<'a> {
type Error = Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_map(visitor)
}
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_map(DocumentMapAccess::new(self.doc, self.input))
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
self.deserialize_map(visitor)
}
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_seq(NodeListSeqAccess {
iter: self.doc.nodes().iter(),
input: self.input,
})
}
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
if self.doc.nodes().is_empty() {
visitor.visit_none()
} else {
visitor.visit_some(self)
}
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_newtype_struct(self)
}
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_unit()
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_unit()
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
// 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<String>,
}
impl<'a> DocumentMapAccess<'a> {
fn new(doc: &'a KdlDocument, input: &'a Arc<String>) -> 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<K: DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>, 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<V: DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value, Self::Error> {
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<String>,
}
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_i16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_i32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_i128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_u128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_f32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_f64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_char<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_scalar(visitor)
}
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_unit()
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_unit()
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(
self,
_len: usize,
visitor: V,
) -> Result<V::Value, Self::Error> {
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value, Self::Error> {
self.deserialize_seq(visitor)
}
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_map(NodeMapAccess::new(self.node, self.input, None))
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_map(NodeMapAccess::new(self.node, self.input, Some(fields)))
}
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
// 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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_unit()
}
}
impl<'a> NodeDeserializer<'a> {
fn deserialize_scalar<'de, V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
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<String>,
}
impl<'de, 'a> SeqAccess<'de> for ArgSeqAccess<'a> {
type Error = Error;
fn next_element_seed<T: DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, 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<String>,
}
impl<'de, 'a> SeqAccess<'de> for NodeListSeqAccess<'a> {
type Error = Error;
fn next_element_seed<T: DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, 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<String>,
}
impl<'de, 'a> de::Deserializer<'de> for NodeGroupDeserializer<'a> {
type Error = Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
self.deserialize_seq(visitor)
}
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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<String>,
}
impl<'de, 'a> SeqAccess<'de> for NodeGroupSeqAccess<'a> {
type Error = Error;
fn next_element_seed<T: DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, 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<String>,
}
#[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<String>,
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<K: DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>, 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<V: DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value, Self::Error> {
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<String>,
}
impl<'de, 'a> Deserializer<'de> for ArgsSeqDeserializer<'a> {
type Error = Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_seq(ArgSeqAccess {
iter: self.entries.into_iter(),
input: self.input,
})
}
fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_seq(ArgSeqAccess {
iter: self.entries.into_iter(),
input: self.input,
})
}
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
if self.entries.is_empty() {
visitor.visit_none()
} else {
visitor.visit_some(self)
}
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_bool(false)
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_bool(false)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
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<String>,
}
impl<'de, 'a> de::EnumAccess<'de> for NodeEnumAccess<'a> {
type Error = Error;
type Variant = NodeVariantAccess<'a>;
fn variant_seed<V: DeserializeSeed<'de>>(
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<String>,
}
impl<'de, 'a> de::VariantAccess<'de> for NodeVariantAccess<'a> {
type Error = Error;
fn unit_variant(self) -> Result<(), Self::Error> {
Ok(())
}
fn newtype_variant_seed<T: DeserializeSeed<'de>>(
self,
seed: T,
) -> Result<T::Value, Self::Error> {
seed.deserialize(NodeDeserializer {
node: self.node,
input: self.input,
})
}
fn tuple_variant<V: Visitor<'de>>(
self,
_len: usize,
visitor: V,
) -> Result<V::Value, Self::Error> {
de::Deserializer::deserialize_seq(
NodeDeserializer {
node: self.node,
input: self.input,
},
visitor,
)
}
fn struct_variant<V: Visitor<'de>>(
self,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
de::Deserializer::deserialize_map(
NodeDeserializer {
node: self.node,
input: self.input,
},
visitor,
)
}
}
struct PropertyEnumAccess<'a> {
key: &'a str,
entry: &'a KdlEntry,
input: &'a Arc<String>,
}
impl<'de, 'a> de::EnumAccess<'de> for PropertyEnumAccess<'a> {
type Error = Error;
type Variant = PropertyVariantAccess<'a>;
fn variant_seed<V: DeserializeSeed<'de>>(
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<String>,
}
impl<'de, 'a> de::VariantAccess<'de> for PropertyVariantAccess<'a> {
type Error = Error;
fn unit_variant(self) -> Result<(), Self::Error> {
Ok(())
}
fn newtype_variant_seed<T: DeserializeSeed<'de>>(
self,
seed: T,
) -> Result<T::Value, Self::Error> {
seed.deserialize(ValueDeserializer::new(self.entry, self.input))
}
fn tuple_variant<V: Visitor<'de>>(
self,
_len: usize,
_visitor: V,
) -> Result<V::Value, Self::Error> {
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<V: Visitor<'de>>(
self,
_fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error> {
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<i32>,
}
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<String>,
}
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<String>,
}
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<String>,
}
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<Item>,
}
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<String>,
}
#[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<String, i32> = 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<String>,
}
#[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<String>,
}
#[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<String>,
}
#[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<u16>,
}
#[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<String>,
// },
// Svn {
// #[serde(rename = "#1")]
// url: String,
// rev: Option<String>,
// },
// }
// #[derive(Deserialize, Debug, PartialEq)]
// struct Config {
// #[serde(rename = "upstream")]
// upstreams: Vec<Upstream>,
// }
// 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::<Config>(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",
);
}
}
}