This commit is contained in:
Kat Marchán 2026-05-28 18:44:29 -07:00
parent 75d4fa039a
commit 65dc03b3dd
5 changed files with 92 additions and 21 deletions

View File

@ -28,7 +28,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
rust: [1.96, stable] rust: [1.95, stable]
os: [ubuntu-latest, macOS-latest, windows-latest] os: [ubuntu-latest, macOS-latest, windows-latest]
steps: steps:

View File

@ -8,7 +8,7 @@ readme = "README.md"
homepage = "https://kdl.dev" homepage = "https://kdl.dev"
repository = "https://github.com/kdl-org/kdl-rs" repository = "https://github.com/kdl-org/kdl-rs"
keywords = ["kdl", "document", "serialization", "config"] keywords = ["kdl", "document", "serialization", "config"]
rust-version = "1.96" rust-version = "1.95"
edition = "2021" edition = "2021"
[features] [features]

View File

@ -1 +1 @@
msrv = "1.96" msrv = "1.95"

105
src/de.rs
View File

@ -44,7 +44,7 @@ use serde::de::value::StringDeserializer;
use serde::de::{self, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::de::{self, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::Deserialize; use serde::Deserialize;
use crate::{KdlDocument, KdlEntry, KdlNode, KdlValue}; use crate::{KdlDocument, KdlEntry, KdlIdentifier, KdlNode, KdlValue};
/// Errors that can occur during KDL deserialization. /// Errors that can occur during KDL deserialization.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -111,6 +111,49 @@ where
T::deserialize(de) T::deserialize(de)
} }
struct IdentDeserializer<'a> {
ident: &'a KdlIdentifier,
}
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> {
visitor.visit_str(self.ident.value())
}
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
visitor.visit_string(self.ident.value().into())
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
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> {
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
}
}
struct ValueDeserializer<'a> { struct ValueDeserializer<'a> {
value: &'a KdlValue, value: &'a KdlValue,
} }
@ -698,6 +741,7 @@ struct NodeMapAccess<'a> {
} }
enum NodeMapValue<'a> { enum NodeMapValue<'a> {
Ident(&'a KdlIdentifier),
Arg(&'a KdlValue), Arg(&'a KdlValue),
Args(Vec<&'a KdlEntry>), Args(Vec<&'a KdlEntry>),
SingleNode(&'a KdlNode), SingleNode(&'a KdlNode),
@ -708,6 +752,10 @@ impl<'a> NodeMapAccess<'a> {
fn new(node: &'a KdlNode) -> Self { fn new(node: &'a KdlNode) -> Self {
let mut entries: Vec<(Cow<'a, str>, NodeMapValue<'a>)> = Vec::new(); let mut entries: Vec<(Cow<'a, str>, NodeMapValue<'a>)> = Vec::new();
entries.push(("#name".into(), NodeMapValue::Ident(node.name())));
if let Some(ty) = node.ty() {
entries.push(("#type".into(), NodeMapValue::Ident(ty)))
}
let args: Vec<_> = node let args: Vec<_> = node
.entries() .entries()
.iter() .iter()
@ -720,19 +768,25 @@ impl<'a> NodeMapAccess<'a> {
.collect(); .collect();
for (i, arg) in args.iter().enumerate() { for (i, arg) in args.iter().enumerate() {
entries.push(( entries.push((format!("#{}", i).into(), NodeMapValue::Arg(arg.value())));
format!("$argument{}", i + 1).into(), if let Some(ty) = arg.ty() {
NodeMapValue::Arg(arg.value()), entries.push((format!("#{}#type", i).into(), NodeMapValue::Ident(ty)));
)); }
} }
if !args.is_empty() { if !args.is_empty() {
entries.push(("$arguments".into(), NodeMapValue::Args(args.clone()))); entries.push(("#args".into(), NodeMapValue::Args(args.clone())));
} }
for prop in &props { for prop in &props {
let name = prop.name().unwrap().value(); let name = prop.name().unwrap().value();
entries.push((format!("@{}", name).into(), NodeMapValue::Arg(prop.value()))); entries.push((
format!("#@{}", name).into(),
NodeMapValue::Arg(prop.value()),
));
if let Some(ty) = prop.ty() {
entries.push((format!("#@{}#type", name).into(), NodeMapValue::Ident(ty)));
}
entries.push((name.into(), NodeMapValue::Arg(prop.value()))); entries.push((name.into(), NodeMapValue::Arg(prop.value())));
} }
@ -786,6 +840,7 @@ impl<'de, 'a> MapAccess<'de> for NodeMapAccess<'a> {
let (_, ref value) = self.entries[self.idx]; let (_, ref value) = self.entries[self.idx];
self.idx += 1; self.idx += 1;
match value { match value {
NodeMapValue::Ident(ident) => seed.deserialize(IdentDeserializer { ident }),
NodeMapValue::Arg(v) => seed.deserialize(ValueDeserializer { value: v }), NodeMapValue::Arg(v) => seed.deserialize(ValueDeserializer { value: v }),
NodeMapValue::Args(entries) => seed.deserialize(ArgsSeqDeserializer { NodeMapValue::Args(entries) => seed.deserialize(ArgsSeqDeserializer {
entries: entries.clone(), entries: entries.clone(),
@ -1346,9 +1401,9 @@ h 8
fn rename_props() { fn rename_props() {
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
struct Server { struct Server {
#[serde(rename = "@host")] #[serde(rename = "#@host")]
host: String, host: String,
#[serde(rename = "@port")] #[serde(rename = "#@port")]
port: u16, port: u16,
} }
@ -1372,13 +1427,26 @@ h 8
#[test] #[test]
fn rename_args() { fn rename_args() {
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
enum HostType {
IpAddr,
Hostname
}
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
struct Server { struct Server {
#[serde(rename = "$argument1")] #[serde(rename = "#type")]
server_type: String,
#[serde(rename = "#0#type")]
host_type: HostType,
#[serde(rename = "#0")]
host: String, host: String,
#[serde(rename = "@port")] #[serde(rename = "#@port#type")]
port_type: String,
#[serde(rename = "#@port")]
port: u16, port: u16,
#[serde(rename = "$arguments")] #[serde(rename = "#args")]
all: Vec<String>, all: Vec<String>,
} }
@ -1387,13 +1455,16 @@ h 8
server: Server, server: Server,
} }
let kdl = "server localhost port=8080 remote"; let kdl = "(linux-debian-arm64)server (hostname)localhost port=(number)8080 remote";
let config: Config = from_str(kdl).unwrap(); let config: Config = from_str(kdl).unwrap();
assert_eq!( assert_eq!(
config, config,
Config { Config {
server: Server { server: Server {
server_type: "linux-debian-arm64".into(),
host_type: HostType::Hostname,
host: "localhost".into(), host: "localhost".into(),
port_type: "number".into(),
port: 8080, port: 8080,
all: Vec::from(["localhost".into(), "remote".into()]) all: Vec::from(["localhost".into(), "remote".into()])
}, },
@ -1405,9 +1476,9 @@ h 8
fn rename_all_args() { fn rename_all_args() {
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
struct Command { struct Command {
#[serde(rename = "@name")] #[serde(rename = "#@name")]
name: String, name: String,
#[serde(rename = "$arguments")] #[serde(rename = "#args")]
args: Vec<String>, args: Vec<String>,
} }
@ -1433,9 +1504,9 @@ h 8
fn rename_children_args() { fn rename_children_args() {
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
struct Server { struct Server {
#[serde(rename = "@host")] #[serde(rename = "#@host")]
host: String, host: String,
#[serde(rename = "$arguments")] #[serde(rename = "#args")]
ports: Vec<u16>, ports: Vec<u16>,
} }

View File

@ -140,7 +140,7 @@
//! //!
//! ## Minimum Supported Rust Version (MSRV) //! ## Minimum Supported Rust Version (MSRV)
//! //!
//! You must be at least `1.96` tall to get on this ride. //! You must be at least `1.95` tall to get on this ride.
//! //!
//! ## License //! ## License
//! //!