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 }}
strategy:
matrix:
rust: [1.96, stable]
rust: [1.95, stable]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:

View File

@ -8,7 +8,7 @@ readme = "README.md"
homepage = "https://kdl.dev"
repository = "https://github.com/kdl-org/kdl-rs"
keywords = ["kdl", "document", "serialization", "config"]
rust-version = "1.96"
rust-version = "1.95"
edition = "2021"
[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::Deserialize;
use crate::{KdlDocument, KdlEntry, KdlNode, KdlValue};
use crate::{KdlDocument, KdlEntry, KdlIdentifier, KdlNode, KdlValue};
/// Errors that can occur during KDL deserialization.
#[derive(Debug, Clone)]
@ -111,6 +111,49 @@ where
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> {
value: &'a KdlValue,
}
@ -698,6 +741,7 @@ struct NodeMapAccess<'a> {
}
enum NodeMapValue<'a> {
Ident(&'a KdlIdentifier),
Arg(&'a KdlValue),
Args(Vec<&'a KdlEntry>),
SingleNode(&'a KdlNode),
@ -708,6 +752,10 @@ impl<'a> NodeMapAccess<'a> {
fn new(node: &'a KdlNode) -> Self {
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
.entries()
.iter()
@ -720,19 +768,25 @@ impl<'a> NodeMapAccess<'a> {
.collect();
for (i, arg) in args.iter().enumerate() {
entries.push((
format!("$argument{}", i + 1).into(),
NodeMapValue::Arg(arg.value()),
));
entries.push((format!("#{}", i).into(), NodeMapValue::Arg(arg.value())));
if let Some(ty) = arg.ty() {
entries.push((format!("#{}#type", i).into(), NodeMapValue::Ident(ty)));
}
}
if !args.is_empty() {
entries.push(("$arguments".into(), NodeMapValue::Args(args.clone())));
entries.push(("#args".into(), NodeMapValue::Args(args.clone())));
}
for prop in &props {
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())));
}
@ -786,6 +840,7 @@ impl<'de, 'a> MapAccess<'de> for NodeMapAccess<'a> {
let (_, ref value) = self.entries[self.idx];
self.idx += 1;
match value {
NodeMapValue::Ident(ident) => seed.deserialize(IdentDeserializer { ident }),
NodeMapValue::Arg(v) => seed.deserialize(ValueDeserializer { value: v }),
NodeMapValue::Args(entries) => seed.deserialize(ArgsSeqDeserializer {
entries: entries.clone(),
@ -1346,9 +1401,9 @@ h 8
fn rename_props() {
#[derive(Deserialize, Debug, PartialEq)]
struct Server {
#[serde(rename = "@host")]
#[serde(rename = "#@host")]
host: String,
#[serde(rename = "@port")]
#[serde(rename = "#@port")]
port: u16,
}
@ -1372,13 +1427,26 @@ h 8
#[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 = "$argument1")]
#[serde(rename = "#type")]
server_type: String,
#[serde(rename = "#0#type")]
host_type: HostType,
#[serde(rename = "#0")]
host: String,
#[serde(rename = "@port")]
#[serde(rename = "#@port#type")]
port_type: String,
#[serde(rename = "#@port")]
port: u16,
#[serde(rename = "$arguments")]
#[serde(rename = "#args")]
all: Vec<String>,
}
@ -1387,13 +1455,16 @@ h 8
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();
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()])
},
@ -1405,9 +1476,9 @@ h 8
fn rename_all_args() {
#[derive(Deserialize, Debug, PartialEq)]
struct Command {
#[serde(rename = "@name")]
#[serde(rename = "#@name")]
name: String,
#[serde(rename = "$arguments")]
#[serde(rename = "#args")]
args: Vec<String>,
}
@ -1433,9 +1504,9 @@ h 8
fn rename_children_args() {
#[derive(Deserialize, Debug, PartialEq)]
struct Server {
#[serde(rename = "@host")]
#[serde(rename = "#@host")]
host: String,
#[serde(rename = "$arguments")]
#[serde(rename = "#args")]
ports: Vec<u16>,
}

View File

@ -140,7 +140,7 @@
//!
//! ## 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
//!