Make integration tests data-driven
Refactor 3191-line test file to 2059 lines (35% reduction) while
increasing test count from 175 to 185 by introducing macros:
- deser_ok!: deserialize KDL and check expected value
- deser_err!: deserialize KDL and expect failure
- deser_field!: deserialize into a single-field wrapper struct
- deser_field_err!: same but expect failure
- deser_repeated_vec!: test repeated nodes into Vec<T>
- roundtrip!: serialize then deserialize back
- ser_err!: serialize and expect failure
- test_untagged_any!: test FieldDeserializer::deserialize_any branches
- test_custom_str_deser!: custom Deserialize calling deserialize_str
- test_custom_bytes_deser!: custom Deserialize calling deserialize_bytes
- test_custom_seq_bytes_deser!: custom Deserialize calling
deserialize_bytes/byte_buf with visit_seq
Adding a new test case for an existing pattern is now a single line
instead of 10-20 lines of boilerplate.
diff --git a/tests/integration.rs b/tests/integration.rs
index b2ab430..49fe665 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -1,45 +1,125 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
-// ── Basic struct ───────────────────────────────────────────────────────
+// ════════════════════════════════════════════════════════════════════════
+// Macros for data-driven tests
+// ════════════════════════════════════════════════════════════════════════
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct SimpleConfig {
- title: String,
- count: i32,
- enabled: bool,
- ratio: f64,
+/// Test that deserializing KDL input produces the expected value.
+macro_rules! deser_ok {
+ ($name:ident, $ty:ty, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ let val: $ty = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val, $expected);
+ }
+ };
}
-#[test]
-fn deserialize_simple_struct() {
- let input = r#"
-title "My App"
-count 42
-enabled #true
-ratio 3.14
-"#;
- let config: SimpleConfig = serde_kdl::from_str(input).unwrap();
- assert_eq!(config.title, "My App");
- assert_eq!(config.count, 42);
- assert_eq!(config.enabled, true);
- assert_eq!(config.ratio, 3.14);
+/// Test that deserializing KDL input into `T` fails.
+macro_rules! deser_err {
+ ($name:ident, $ty:ty, $input:expr) => {
+ #[test]
+ fn $name() {
+ assert!(serde_kdl::from_str::<$ty>($input).is_err());
+ }
+ };
}
-#[test]
-fn serialize_simple_struct() {
- let config = SimpleConfig {
- title: "My App".into(),
- count: 42,
- enabled: true,
- ratio: 3.14,
+/// Test that a value roundtrips through serialize → deserialize.
+macro_rules! roundtrip {
+ ($name:ident, $ty:ty, $val:expr) => {
+ #[test]
+ fn $name() {
+ let val: $ty = $val;
+ let output = serde_kdl::to_string(&val).unwrap();
+ let rt: $ty = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(val, rt);
+ }
+ };
+}
+
+/// Test that serializing a value fails.
+macro_rules! ser_err {
+ ($name:ident, $val:expr) => {
+ #[test]
+ fn $name() {
+ assert!(serde_kdl::to_string(&$val).is_err());
+ }
+ };
+}
+
+/// Test deserializing repeated nodes into Vec<T>.
+/// Defines a wrapper struct with the given field name internally.
+macro_rules! deser_repeated_vec {
+ ($name:ident, $field:ident: Vec<$ty:ty>, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct W {
+ $field: Vec<$ty>,
+ }
+ let val: W = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val.$field, $expected);
+ }
+ };
+}
+
+/// Test that deserializing repeated nodes into Vec<T> fails.
+macro_rules! deser_repeated_vec_err {
+ ($name:ident, $field:ident: Vec<$ty:ty>, $input:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct W {
+ $field: Vec<$ty>,
+ }
+ assert!(serde_kdl::from_str::<W>($input).is_err());
+ }
+ };
+}
+
+/// Test deserializing into a wrapper with a single typed field.
+macro_rules! deser_field {
+ ($name:ident, $field:ident: $ty:ty, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct W {
+ $field: $ty,
+ }
+ let val: W = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val.$field, $expected);
+ }
+ };
+}
+
+/// Test that deserializing into a wrapper with a single typed field fails.
+macro_rules! deser_field_err {
+ ($name:ident, $field:ident: $ty:ty, $input:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, Deserialize)]
+ struct W {
+ #[allow(dead_code)]
+ $field: $ty,
+ }
+ assert!(serde_kdl::from_str::<W>($input).is_err());
+ }
};
- let output = serde_kdl::to_string(&config).unwrap();
- let roundtrip: SimpleConfig = serde_kdl::from_str(&output).unwrap();
- assert_eq!(config, roundtrip);
}
-// ── Nested struct ──────────────────────────────────────────────────────
+// ════════════════════════════════════════════════════════════════════════
+// Shared types
+// ════════════════════════════════════════════════════════════════════════
+
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+struct SimpleConfig {
+ title: String,
+ count: i32,
+ enabled: bool,
+ ratio: f64,
+}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Server {
@@ -53,34 +133,79 @@ struct AppConfig {
server: Server,
}
-#[test]
-fn deserialize_nested_struct() {
- let input = r#"
-name "webapp"
-server {
- host "localhost"
- port 8080
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+enum Color {
+ Red,
+ Green,
+ Blue,
+}
+
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+struct Colored {
+ name: String,
+ color: Color,
}
-"#;
- let config: AppConfig = serde_kdl::from_str(input).unwrap();
- assert_eq!(config.name, "webapp");
- assert_eq!(config.server.host, "localhost");
- assert_eq!(config.server.port, 8080);
+
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+enum Shape {
+ Circle { radius: f64 },
+ Rectangle { width: f64, height: f64 },
}
-#[test]
-fn serialize_nested_struct() {
- let config = AppConfig {
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+struct UnitStruct;
+
+// ════════════════════════════════════════════════════════════════════════
+// Basic feature tests
+// ════════════════════════════════════════════════════════════════════════
+
+deser_ok!(
+ deserialize_simple_struct,
+ SimpleConfig,
+ "title \"My App\"\ncount 42\nenabled #true\nratio 3.14\n",
+ SimpleConfig {
+ title: "My App".into(),
+ count: 42,
+ enabled: true,
+ ratio: 3.14
+ }
+);
+
+roundtrip!(
+ serialize_simple_struct,
+ SimpleConfig,
+ SimpleConfig {
+ title: "My App".into(),
+ count: 42,
+ enabled: true,
+ ratio: 3.14,
+ }
+);
+
+deser_ok!(
+ deserialize_nested_struct,
+ AppConfig,
+ "name \"webapp\"\nserver {\n host \"localhost\"\n port 8080\n}\n",
+ AppConfig {
name: "webapp".into(),
server: Server {
host: "localhost".into(),
port: 8080,
},
- };
- let output = serde_kdl::to_string(&config).unwrap();
- let roundtrip: AppConfig = serde_kdl::from_str(&output).unwrap();
- assert_eq!(config, roundtrip);
-}
+ }
+);
+
+roundtrip!(
+ serialize_nested_struct,
+ AppConfig,
+ AppConfig {
+ name: "webapp".into(),
+ server: Server {
+ host: "localhost".into(),
+ port: 8080,
+ },
+ }
+);
// ── Vec of primitives ──────────────────────────────────────────────────
@@ -90,93 +215,75 @@ struct Tagged {
tags: Vec<String>,
}
-#[test]
-fn deserialize_vec_primitives() {
- let input = r#"
-name "project"
-tags "web" "rust" "config"
-"#;
- let tagged: Tagged = serde_kdl::from_str(input).unwrap();
- assert_eq!(tagged.name, "project");
- assert_eq!(tagged.tags, vec!["web", "rust", "config"]);
-}
+deser_ok!(
+ deserialize_vec_primitives,
+ Tagged,
+ "name \"project\"\ntags \"web\" \"rust\" \"config\"\n",
+ Tagged {
+ name: "project".into(),
+ tags: vec!["web".into(), "rust".into(), "config".into()],
+ }
+);
-#[test]
-fn serialize_vec_primitives() {
- let tagged = Tagged {
+roundtrip!(
+ serialize_vec_primitives,
+ Tagged,
+ Tagged {
name: "project".into(),
tags: vec!["web".into(), "rust".into(), "config".into()],
- };
- let output = serde_kdl::to_string(&tagged).unwrap();
- let roundtrip: Tagged = serde_kdl::from_str(&output).unwrap();
- assert_eq!(tagged, roundtrip);
-}
+ }
+);
-// ── Vec of structs (repeated nodes) ────────────────────────────────────
+// ── Vec of structs ─────────────────────────────────────────────────────
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Cluster {
server: Vec<Server>,
}
-#[test]
-fn deserialize_vec_structs() {
- let input = r#"
-server {
- host "localhost"
- port 8080
-}
-server {
- host "example.com"
- port 443
-}
-"#;
- let cluster: Cluster = serde_kdl::from_str(input).unwrap();
- assert_eq!(cluster.server.len(), 2);
- assert_eq!(cluster.server[0].host, "localhost");
- assert_eq!(cluster.server[0].port, 8080);
- assert_eq!(cluster.server[1].host, "example.com");
- assert_eq!(cluster.server[1].port, 443);
-}
+deser_ok!(
+ deserialize_vec_structs,
+ Cluster,
+ "server {\n host \"localhost\"\n port 8080\n}\nserver {\n host \"example.com\"\n port 443\n}\n",
+ Cluster {
+ server: vec![
+ Server {
+ host: "localhost".into(),
+ port: 8080
+ },
+ Server {
+ host: "example.com".into(),
+ port: 443
+ },
+ ],
+ }
+);
-#[test]
-fn serialize_vec_structs() {
- let cluster = Cluster {
+roundtrip!(
+ serialize_vec_structs,
+ Cluster,
+ Cluster {
server: vec![
Server {
host: "localhost".into(),
- port: 8080,
+ port: 8080
},
Server {
host: "example.com".into(),
- port: 443,
+ port: 443
},
],
- };
- let output = serde_kdl::to_string(&cluster).unwrap();
- let roundtrip: Cluster = serde_kdl::from_str(&output).unwrap();
- assert_eq!(cluster, roundtrip);
-}
+ }
+);
-// ── Dash children convention ───────────────────────────────────────────
+// ── Dash children ──────────────────────────────────────────────────────
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct DashList {
+deser_field!(
+ deserialize_dash_children,
items: Vec<i32>,
-}
-
-#[test]
-fn deserialize_dash_children() {
- let input = r#"
-items {
- - 1
- - 2
- - 3
-}
-"#;
- let list: DashList = serde_kdl::from_str(input).unwrap();
- assert_eq!(list.items, vec![1, 2, 3]);
-}
+ "items {\n - 1\n - 2\n - 3\n}\n",
+ vec![1, 2, 3]
+);
// ── Option fields ──────────────────────────────────────────────────────
@@ -186,138 +293,107 @@ struct OptionalFields {
optional: Option<String>,
}
-#[test]
-fn deserialize_option_present() {
- let input = r#"
-required "hello"
-optional "world"
-"#;
- let val: OptionalFields = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.required, "hello");
- assert_eq!(val.optional, Some("world".into()));
-}
+deser_ok!(
+ deserialize_option_present,
+ OptionalFields,
+ "required \"hello\"\noptional \"world\"\n",
+ OptionalFields {
+ required: String::from("hello"),
+ optional: Some(String::from("world"))
+ }
+);
-#[test]
-fn deserialize_option_absent() {
- let input = r#"
-required "hello"
-"#;
- let val: OptionalFields = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.required, "hello");
- assert_eq!(val.optional, None);
-}
+deser_ok!(
+ deserialize_option_absent,
+ OptionalFields,
+ "required \"hello\"\n",
+ OptionalFields {
+ required: String::from("hello"),
+ optional: None
+ }
+);
-#[test]
-fn deserialize_option_null() {
- let input = r#"
-required "hello"
-optional #null
-"#;
- let val: OptionalFields = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.required, "hello");
- assert_eq!(val.optional, None);
-}
+deser_ok!(
+ deserialize_option_null,
+ OptionalFields,
+ "required \"hello\"\noptional #null\n",
+ OptionalFields {
+ required: String::from("hello"),
+ optional: None
+ }
+);
#[test]
fn serialize_option() {
let with = OptionalFields {
- required: "hello".into(),
- optional: Some("world".into()),
+ required: String::from("hello"),
+ optional: Some(String::from("world")),
};
let output = serde_kdl::to_string(&with).unwrap();
assert!(output.contains("optional"));
- let roundtrip: OptionalFields = serde_kdl::from_str(&output).unwrap();
- assert_eq!(with, roundtrip);
+ let rt: OptionalFields = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(with, rt);
let without = OptionalFields {
- required: "hello".into(),
+ required: String::from("hello"),
optional: None,
};
let output = serde_kdl::to_string(&without).unwrap();
- // None fields should be omitted
assert!(!output.contains("optional"));
- let roundtrip: OptionalFields = serde_kdl::from_str(&output).unwrap();
- assert_eq!(without, roundtrip);
+ let rt: OptionalFields = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(without, rt);
}
// ── Enum variants ──────────────────────────────────────────────────────
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-enum Color {
- Red,
- Green,
- Blue,
-}
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct Colored {
- name: String,
- color: Color,
-}
-
-#[test]
-fn deserialize_unit_variant() {
- let input = r#"
-name "widget"
-color "Red"
-"#;
- let val: Colored = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.color, Color::Red);
-}
+deser_ok!(
+ deserialize_unit_variant,
+ Colored,
+ "name \"widget\"\ncolor \"Red\"\n",
+ Colored {
+ name: "widget".into(),
+ color: Color::Red
+ }
+);
-#[test]
-fn serialize_unit_variant() {
- let val = Colored {
+roundtrip!(
+ serialize_unit_variant,
+ Colored,
+ Colored {
name: "widget".into(),
- color: Color::Green,
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Colored = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
+ color: Color::Green
+ }
+);
// ── Struct variant enum ────────────────────────────────────────────────
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-enum Shape {
- Circle { radius: f64 },
- Rectangle { width: f64, height: f64 },
-}
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Drawing {
name: String,
shape: Shape,
}
-#[test]
-fn deserialize_struct_variant() {
- let input = r#"
-name "my drawing"
-shape {
- Circle {
- radius 5.0
+deser_ok!(
+ deserialize_struct_variant,
+ Drawing,
+ "name \"my drawing\"\nshape {\n Circle {\n radius 5.0\n }\n}\n",
+ Drawing {
+ name: "my drawing".into(),
+ shape: Shape::Circle { radius: 5.0 }
}
-}
-"#;
- let val: Drawing = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, "my drawing");
- assert_eq!(val.shape, Shape::Circle { radius: 5.0 });
-}
+);
-#[test]
-fn serialize_struct_variant() {
- let val = Drawing {
+roundtrip!(
+ serialize_struct_variant,
+ Drawing,
+ Drawing {
name: "my drawing".into(),
shape: Shape::Rectangle {
width: 10.0,
- height: 20.0,
+ height: 20.0
},
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Drawing = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
+ }
+);
// ── Newtype variant enum ───────────────────────────────────────────────
@@ -332,26 +408,22 @@ struct Wrapped {
value: Wrapper,
}
-#[test]
-fn deserialize_newtype_variant() {
- let input = r#"
-value {
- Text "hello"
-}
-"#;
- let val: Wrapped = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.value, Wrapper::Text("hello".into()));
-}
+deser_ok!(
+ deserialize_newtype_variant,
+ Wrapped,
+ "value {\n Text \"hello\"\n}\n",
+ Wrapped {
+ value: Wrapper::Text(String::from("hello"))
+ }
+);
-#[test]
-fn serialize_newtype_variant() {
- let val = Wrapped {
- value: Wrapper::Text("hello".into()),
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Wrapped = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
+roundtrip!(
+ serialize_newtype_variant,
+ Wrapped,
+ Wrapped {
+ value: Wrapper::Text(String::from("hello"))
+ }
+);
// ── HashMap ────────────────────────────────────────────────────────────
@@ -362,13 +434,8 @@ struct WithMap {
#[test]
fn deserialize_hashmap() {
- let input = r#"
-settings {
- key1 "value1"
- key2 "value2"
-}
-"#;
- let val: WithMap = serde_kdl::from_str(input).unwrap();
+ let val: WithMap =
+ serde_kdl::from_str("settings {\n key1 \"value1\"\n key2 \"value2\"\n}\n").unwrap();
assert_eq!(val.settings.get("key1"), Some(&"value1".into()));
assert_eq!(val.settings.get("key2"), Some(&"value2".into()));
}
@@ -379,8 +446,8 @@ fn serialize_hashmap() {
settings.insert("key1".into(), "value1".into());
let val = WithMap { settings };
let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithMap = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
+ let rt: WithMap = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(val, rt);
}
// ── Various integer types ──────────────────────────────────────────────
@@ -397,9 +464,10 @@ struct IntTypes {
h: i64,
}
-#[test]
-fn roundtrip_int_types() {
- let val = IntTypes {
+roundtrip!(
+ roundtrip_int_types,
+ IntTypes,
+ IntTypes {
a: 1,
b: 2,
c: 3,
@@ -407,37 +475,26 @@ fn roundtrip_int_types() {
e: -1,
f: -2,
g: -3,
- h: -4,
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: IntTypes = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
+ h: -4
+ }
+);
// ── Tuple ──────────────────────────────────────────────────────────────
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithTuple {
- point: (f64, f64, f64),
-}
-
-#[test]
-fn deserialize_tuple() {
- let input = r#"
-point 1.0 2.0 3.0
-"#;
- let val: WithTuple = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.point, (1.0, 2.0, 3.0));
-}
+deser_field!(deserialize_tuple, point: (f64, f64, f64), "point 1.0 2.0 3.0", (1.0, 2.0, 3.0));
#[test]
fn roundtrip_tuple() {
- let val = WithTuple {
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
+ struct W {
+ point: (f64, f64, f64),
+ }
+ let val = W {
point: (1.0, 2.0, 3.0),
};
let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithTuple = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
+ let rt: W = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(val, rt);
}
// ── Deeply nested ──────────────────────────────────────────────────────
@@ -446,12 +503,10 @@ fn roundtrip_tuple() {
struct Level3 {
value: String,
}
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Level2 {
inner: Level3,
}
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Level1 {
middle: Level2,
@@ -459,22 +514,15 @@ struct Level1 {
#[test]
fn deeply_nested() {
- let input = r#"
-middle {
- inner {
- value "deep"
- }
-}
-"#;
+ let input = "middle {\n inner {\n value \"deep\"\n }\n}\n";
let val: Level1 = serde_kdl::from_str(input).unwrap();
assert_eq!(val.middle.inner.value, "deep");
-
let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Level1 = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
+ let rt: Level1 = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(val, rt);
}
-// ── Boolean values ─────────────────────────────────────────────────────
+// ── Booleans ───────────────────────────────────────────────────────────
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Booleans {
@@ -482,35 +530,36 @@ struct Booleans {
no: bool,
}
-#[test]
-fn booleans() {
- let input = r#"
-yes #true
-no #false
-"#;
- let val: Booleans = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.yes, true);
- assert_eq!(val.no, false);
-
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Booleans = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
+deser_ok!(
+ booleans_deser,
+ Booleans,
+ "yes #true\nno #false\n",
+ Booleans {
+ yes: true,
+ no: false
+ }
+);
+roundtrip!(
+ booleans_roundtrip,
+ Booleans,
+ Booleans {
+ yes: true,
+ no: false
+ }
+);
// ── Empty vec ──────────────────────────────────────────────────────────
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithVec {
- items: Vec<String>,
-}
-
#[test]
fn serialize_empty_vec() {
- let val = WithVec { items: vec![] };
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
+ struct W {
+ items: Vec<String>,
+ }
+ let val = W { items: vec![] };
let output = serde_kdl::to_string(&val).unwrap();
- // Empty vec gets an empty children block
- let roundtrip: WithVec = serde_kdl::from_str(&output).unwrap();
- assert_eq!(roundtrip.items, Vec::<String>::new());
+ let rt: W = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(rt.items, Vec::<String>::new());
}
// ── to_string_pretty ───────────────────────────────────────────────────
@@ -525,9 +574,8 @@ fn pretty_print() {
},
};
let pretty = serde_kdl::to_string_pretty(&config).unwrap();
- // Should still roundtrip
- let roundtrip: AppConfig = serde_kdl::from_str(&pretty).unwrap();
- assert_eq!(config, roundtrip);
+ let rt: AppConfig = serde_kdl::from_str(&pretty).unwrap();
+ assert_eq!(config, rt);
}
// ── to_doc / from_doc ──────────────────────────────────────────────────
@@ -542,1500 +590,307 @@ fn doc_api() {
};
let doc = serde_kdl::to_doc(&config).unwrap();
assert!(doc.get("title").is_some());
- let roundtrip: SimpleConfig = serde_kdl::from_doc(&doc).unwrap();
- assert_eq!(config, roundtrip);
+ let rt: SimpleConfig = serde_kdl::from_doc(&doc).unwrap();
+ assert_eq!(config, rt);
}
-// ── Vec of integers ────────────────────────────────────────────────────
+// ════════════════════════════════════════════════════════════════════════
+// Roundtrip tests — data-driven
+// ════════════════════════════════════════════════════════════════════════
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct Numbers {
+struct W_vi {
values: Vec<i32>,
}
-
-#[test]
-fn roundtrip_vec_ints() {
- let val = Numbers {
- values: vec![1, 2, 3, 4, 5],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Numbers = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-// ── Char field ─────────────────────────────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithChar {
+struct W_ch {
letter: char,
}
-
-#[test]
-fn roundtrip_char() {
- let val = WithChar { letter: 'X' };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithChar = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+struct W_bi {
+ big_signed: i128,
+ big_unsigned: u128,
}
-
-// ════════════════════════════════════════════════════════════════════════
-// Coverage expansion tests
-// ════════════════════════════════════════════════════════════════════════
-
-// ── Error trait impls ──────────────────────────────────────────────────
-
-#[test]
-fn error_display_variants() {
- let err = serde_kdl::Error::TopLevelNotStruct;
- assert_eq!(err.to_string(), "top-level type must be a struct or map");
-
- let err = serde_kdl::Error::Message("custom error".into());
- assert_eq!(err.to_string(), "custom error");
-
- let err = serde_kdl::Error::TypeMismatch {
- expected: "string",
- got: "integer".into(),
- };
- assert!(err.to_string().contains("expected string"));
-
- let err = serde_kdl::Error::MissingField("name".into());
- assert!(err.to_string().contains("name"));
-
- let err = serde_kdl::Error::IntegerOutOfRange(999999);
- assert!(err.to_string().contains("999999"));
-
- let err = serde_kdl::Error::UnknownVariant("Foo".into());
- assert!(err.to_string().contains("Foo"));
-
- let err = serde_kdl::Error::Unsupported("nope".into());
- assert!(err.to_string().contains("nope"));
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+struct W_f32 {
+ value: f32,
}
-
-#[test]
-fn serde_error_custom_impls() {
- // Exercise serde::de::Error::custom
- let err = <serde_kdl::Error as serde::de::Error>::custom("deser fail");
- assert_eq!(err.to_string(), "deser fail");
-
- // Exercise serde::ser::Error::custom
- let err = <serde_kdl::Error as serde::ser::Error>::custom("ser fail");
- assert_eq!(err.to_string(), "ser fail");
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+struct W_f64 {
+ value: f64,
}
-
-// ── Serialization: top-level not struct ────────────────────────────────
-
-#[test]
-fn serialize_top_level_not_struct() {
- let result = serde_kdl::to_string(&42i32);
- assert!(result.is_err());
- let err = result.unwrap_err();
- assert!(matches!(err, serde_kdl::Error::TopLevelNotStruct));
-}
-
-#[test]
-fn serialize_top_level_string_err() {
- let result = serde_kdl::to_string(&"hello");
- assert!(result.is_err());
-}
-
-#[test]
-fn serialize_top_level_bool_err() {
- let result = serde_kdl::to_string(&true);
- assert!(result.is_err());
-}
-
-// ── Serialization: i128 / u128 ────────────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct BigInts {
- big_signed: i128,
- big_unsigned: u128,
-}
-
-#[test]
-fn roundtrip_i128_u128() {
- // Use values within KDL's representable integer range
- let val = BigInts {
- big_signed: -1_000_000_000_000i128,
- big_unsigned: 1_000_000_000_000u128,
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: BigInts = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-// ── Serialization: bytes ───────────────────────────────────────────────
-
-#[test]
-fn serialize_bytes() {
- // serde_bytes triggers serialize_bytes
- #[derive(Serialize, Deserialize, Debug, PartialEq)]
- struct WithBytes {
- #[serde(with = "serde_bytes_helper")]
- data: Vec<u8>,
- }
-
- // Manually implement a bytes serializer/deserializer to exercise the path
- mod serde_bytes_helper {
- use serde::{Deserializer, Serializer};
-
- pub fn serialize<S: Serializer>(data: &[u8], ser: S) -> Result<S::Ok, S::Error> {
- ser.serialize_bytes(data)
- }
-
- pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<u8>, D::Error> {
- use serde::Deserialize;
- Vec::<u8>::deserialize(de)
- }
- }
-
- let val = WithBytes {
- data: vec![1, 2, 3],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithBytes = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
+struct W_i128 {
+ value: i128,
}
-
-// ── Serialization: unit and unit_struct ────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct UnitStruct;
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct ContainsUnit {
- label: String,
- marker: (),
-}
-
-#[test]
-fn serialize_unit_value() {
- let val = ContainsUnit {
- label: "test".into(),
- marker: (),
- };
- // Unit serializes as Null, which gets skipped as None
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("label"));
+struct W_u128 {
+ value: u128,
}
-
-// ── Serialization: newtype struct ──────────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Meters(f64);
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct Distance {
+struct W_nt {
length: Meters,
}
-
-#[test]
-fn roundtrip_newtype_struct() {
- let val = Distance {
- length: Meters(42.5),
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: Distance = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-// ── Serialization: tuple variant ───────────────────────────────────────
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-enum Data {
- Point(f64, f64, f64),
- Pair(String, i32),
-}
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithData {
- data: Data,
-}
-
-#[test]
-fn roundtrip_tuple_variant() {
- let val = WithData {
- data: Data::Point(1.0, 2.0, 3.0),
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithData = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-#[test]
-fn roundtrip_tuple_variant_pair() {
- let val = WithData {
- data: Data::Pair("hello".into(), 42),
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithData = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-// ── Serialization: tuple struct ────────────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Point3D(f64, f64, f64);
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithTupleStruct {
+struct W_ts {
pos: Point3D,
}
-#[test]
-fn roundtrip_tuple_struct() {
- let val = WithTupleStruct {
- pos: Point3D(1.0, 2.0, 3.0),
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithTupleStruct = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-// ── Serialization: mixed sequences ─────────────────────────────────────
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct MixedSeq {
- items: Vec<serde_json_like::Value>,
-}
-
-// A minimal enum to produce mixed sequences (primitives + maps).
-mod serde_json_like {
- use serde::{Deserialize, Serialize};
-
- #[derive(Debug, Serialize, Deserialize, PartialEq)]
- #[serde(untagged)]
- pub enum Value {
- Num(i64),
- Str(String),
- }
-}
-
-#[test]
-fn serialize_mixed_sequence() {
- // When a sequence contains mixed types that all serialize as primitives,
- // they should still produce a single node with multiple args.
- let val = MixedSeq {
- items: vec![
- serde_json_like::Value::Num(1),
- serde_json_like::Value::Num(2),
- ],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("items"));
-}
-
-// ── Serialization: map with integer keys ───────────────────────────────
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct IntKeyMap {
- lookup: HashMap<i32, String>,
-}
-
-#[test]
-fn serialize_map_integer_keys() {
- let mut lookup = HashMap::new();
- lookup.insert(1, "one".into());
- lookup.insert(2, "two".into());
- let val = IntKeyMap { lookup };
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("1") || output.contains("2"));
-}
-
-// ── Serialization: map with bool keys ──────────────────────────────────
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct BoolKeyMap {
- flags: HashMap<bool, String>,
-}
-
-#[test]
-fn serialize_map_bool_keys() {
- let mut flags = HashMap::new();
- flags.insert(true, "yes".into());
- let val = BoolKeyMap { flags };
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("true"));
-}
-
-// ── Deserialization: f32 field ─────────────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithF32 {
- value: f32,
-}
-
-#[test]
-fn roundtrip_f32() {
- let val = WithF32 { value: 3.14 };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithF32 = serde_kdl::from_str(&output).unwrap();
- assert!((roundtrip.value - 3.14).abs() < 0.001);
-}
-
-#[test]
-fn deserialize_f32_from_integer() {
- let input = r#"value 3"#;
- let val: WithF32 = serde_kdl::from_str(input).unwrap();
- assert!((val.value - 3.0).abs() < 0.001);
+enum Data {
+ Point(f64, f64, f64),
+ Pair(String, i32),
}
-
-// ── Deserialization: f64 from integer ──────────────────────────────────
-
#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithF64 {
- value: f64,
-}
-
-#[test]
-fn deserialize_f64_from_integer() {
- let input = r#"value 42"#;
- let val: WithF64 = serde_kdl::from_str(input).unwrap();
- assert!((val.value - 42.0).abs() < 0.001);
-}
-
-// ── Deserialization: integer from float ────────────────────────────────
-
-#[test]
-fn deserialize_int_from_float() {
- let input = r#"count 3.0"#;
- #[derive(Deserialize)]
- struct S {
- count: i32,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.count, 3);
-}
-
-// ── Deserialization: integer overflow ──────────────────────────────────
-
-#[test]
-fn deserialize_integer_overflow() {
- let input = r#"value 999"#;
- #[derive(Deserialize)]
- struct S {
- value: i8,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-// ── Deserialization: type mismatches ───────────────────────────────────
-
-#[test]
-fn deserialize_bool_type_mismatch() {
- let input = r#"flag "not a bool""#;
- #[derive(Deserialize)]
- struct S {
- flag: bool,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-#[test]
-fn deserialize_string_type_mismatch() {
- let input = r#"name 42"#;
- #[derive(Deserialize)]
- struct S {
- name: String,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-#[test]
-fn deserialize_int_type_mismatch() {
- let input = r#"value "not a number""#;
- #[derive(Deserialize)]
- struct S {
- value: i32,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-#[test]
-fn deserialize_float_type_mismatch() {
- let input = r#"value "not a float""#;
- #[derive(Deserialize)]
- struct S {
- value: f64,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-#[test]
-fn deserialize_char_type_mismatch() {
- // Multi-character string can't be a char
- let input = r#"ch "abc""#;
- #[derive(Deserialize)]
- struct S {
- ch: char,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-#[test]
-fn deserialize_char_from_int_mismatch() {
- let input = r#"ch 65"#;
- #[derive(Deserialize)]
- struct S {
- ch: char,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
+struct W_data {
+ data: Data,
}
-// ── Deserialization: KDL parse error ───────────────────────────────────
+roundtrip!(
+ roundtrip_vec_ints,
+ W_vi,
+ W_vi {
+ values: vec![1, 2, 3, 4, 5]
+ }
+);
+roundtrip!(roundtrip_char, W_ch, W_ch { letter: 'X' });
+roundtrip!(
+ roundtrip_i128_u128,
+ W_bi,
+ W_bi {
+ big_signed: -1_000_000_000_000,
+ big_unsigned: 1_000_000_000_000
+ }
+);
+roundtrip!(roundtrip_f32, W_f32, W_f32 { value: 3.14 });
+roundtrip!(
+ roundtrip_i128,
+ W_i128,
+ W_i128 {
+ value: 170_141_183_460_469_231_731_687_303_715_884_105_727i128
+ }
+);
+roundtrip!(roundtrip_u128, W_u128, W_u128 { value: 1000u128 });
+roundtrip!(
+ roundtrip_newtype_struct,
+ W_nt,
+ W_nt {
+ length: Meters(42.5)
+ }
+);
+roundtrip!(
+ roundtrip_tuple_struct,
+ W_ts,
+ W_ts {
+ pos: Point3D(1.0, 2.0, 3.0)
+ }
+);
+roundtrip!(
+ roundtrip_tuple_variant,
+ W_data,
+ W_data {
+ data: Data::Point(1.0, 2.0, 3.0)
+ }
+);
+roundtrip!(
+ roundtrip_tuple_variant_pair,
+ W_data,
+ W_data {
+ data: Data::Pair(String::from("hello"), 42)
+ }
+);
-#[test]
-fn deserialize_invalid_kdl() {
- let result = serde_kdl::from_str::<SimpleConfig>("{{{{invalid");
- assert!(result.is_err());
-}
+// ════════════════════════════════════════════════════════════════════════
+// Serialization-specific tests
+// ════════════════════════════════════════════════════════════════════════
-// ── Deserialization: node with no arguments ────────────────────────────
+ser_err!(serialize_top_level_not_struct, 42i32);
+ser_err!(serialize_top_level_string_err, "hello");
+ser_err!(serialize_top_level_bool_err, true);
#[test]
-fn deserialize_node_no_args() {
- let input = r#"value"#;
- #[derive(Deserialize)]
- struct S {
- value: String,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
+fn serialize_top_level_not_struct_error_type() {
+ let err = serde_kdl::to_string(&42i32).unwrap_err();
+ assert!(matches!(err, serde_kdl::Error::TopLevelNotStruct));
}
-// ── Deserialization: FieldDeserializer::deserialize_any branches ───────
-
#[test]
-fn deserialize_any_with_properties() {
- // Properties on a node → map
- let input = r#"item key="value""#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct Outer {
- item: HashMap<String, String>,
+fn serialize_bytes() {
+ mod serde_bytes_helper {
+ use serde::{Deserializer, Serializer};
+ pub fn serialize<S: Serializer>(data: &[u8], ser: S) -> Result<S::Ok, S::Error> {
+ ser.serialize_bytes(data)
+ }
+ pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<u8>, D::Error> {
+ use serde::Deserialize;
+ Vec::<u8>::deserialize(de)
+ }
}
- let val: Outer = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.item.get("key"), Some(&"value".into()));
-}
-
-#[test]
-fn deserialize_any_with_multiple_args() {
- // Multiple args on a node → seq
- let input = r#"values 1 2 3"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- values: Vec<i32>,
+ #[derive(Serialize, Deserialize, Debug, PartialEq)]
+ struct W {
+ #[serde(with = "serde_bytes_helper")]
+ data: Vec<u8>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.values, vec![1, 2, 3]);
+ let val = W {
+ data: vec![1, 2, 3],
+ };
+ let output = serde_kdl::to_string(&val).unwrap();
+ let rt: W = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(val, rt);
}
#[test]
-fn deserialize_any_unit_node() {
- // Node with no args, no children, no props → unit
- let input = r#"
-marker
-name "test"
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
+fn serialize_unit_value() {
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S {
+ label: String,
marker: (),
- name: String,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, "test");
+ let output = serde_kdl::to_string(&S {
+ label: String::from("test"),
+ marker: (),
+ })
+ .unwrap();
+ assert!(output.contains("label"));
}
-// ── Deserialization: properties-based struct ───────────────────────────
-
-#[test]
-fn deserialize_struct_from_properties() {
- let input = r#"point x=1.0 y=2.0"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct Point {
- x: f64,
- y: f64,
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- point: Point,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.point, Point { x: 1.0, y: 2.0 });
-}
-
-// ── Deserialization: map from properties ───────────────────────────────
-
-#[test]
-fn deserialize_map_from_properties() {
- let input = r#"meta author="Alice" version="1.0""#;
- #[derive(Deserialize, Debug)]
- struct S {
- meta: HashMap<String, String>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.meta.get("author"), Some(&"Alice".into()));
- assert_eq!(val.meta.get("version"), Some(&"1.0".into()));
-}
-
-// ── Deserialization: empty map/struct from node ────────────────────────
-
-#[test]
-fn deserialize_empty_map_from_node() {
- let input = r#"meta"#;
- #[derive(Deserialize, Debug)]
- struct S {
- meta: HashMap<String, String>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert!(val.meta.is_empty());
-}
-
-// ── Deserialization: non-dash children as sequence ─────────────────────
-
-#[test]
-fn deserialize_children_as_sequence() {
- // Children that aren't "-" nodes should still work as sequence elements
- // when the target type is a Vec.
- let input = r#"
-items {
- item 1
- item 2
- item 3
-}
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- items: Vec<i32>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.items, vec![1, 2, 3]);
-}
-
-// ── Deserialization: i128 field ────────────────────────────────────────
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithI128 {
- value: i128,
-}
-
-#[test]
-fn roundtrip_i128() {
- let val = WithI128 {
- value: 170_141_183_460_469_231_731_687_303_715_884_105_727i128,
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithI128 = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-// ── Deserialization: u128 field ────────────────────────────────────────
-
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-struct WithU128 {
- value: u128,
-}
-
-#[test]
-fn roundtrip_u128() {
- let val = WithU128 { value: 1000u128 };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: WithU128 = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-#[test]
-fn deserialize_u128_overflow() {
- let input = r#"value -1"#;
- let result = serde_kdl::from_str::<WithU128>(input);
- assert!(result.is_err());
-}
-
-// ── Deserialization: enum error paths ──────────────────────────────────
-
-#[test]
-fn deserialize_enum_no_match() {
- // Node with integer arg, not a string → can't determine variant
- let input = r#"color 42"#;
- #[derive(Deserialize)]
- struct S {
- color: Color,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-// ── Deserialization: ValueDeserializer paths ───────────────────────────
-
-#[test]
-fn value_deserializer_null_option() {
- let input = r#"
-required "hello"
-optional #null
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- required: String,
- optional: Option<i32>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.required, "hello");
- assert_eq!(val.optional, None);
-}
-
-// ── Deserialization: bytes from value ──────────────────────────────────
-
-#[test]
-fn value_deserializer_bytes_from_string() {
- // The FieldDeserializer::deserialize_bytes delegates to deserialize_seq,
- // so bytes from a string node go through the seq path.
- // Verify the seq path works for byte-like data.
- let input = r#"data 104 101 108"#;
- #[derive(Deserialize, Debug)]
- struct S {
- data: Vec<u8>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, vec![104, 101, 108]);
-}
-
-#[test]
-fn value_deserializer_bytes_type_mismatch() {
- use serde::Deserialize;
-
- #[derive(Debug)]
- struct ByteString(Vec<u8>);
-
- impl<'de> Deserialize<'de> for ByteString {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = ByteString;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "bytes")
- }
- fn visit_bytes<E>(self, v: &[u8]) -> Result<ByteString, E> {
- Ok(ByteString(v.to_vec()))
- }
- }
- de.deserialize_bytes(V)
- }
- }
-
- #[derive(Deserialize, Debug)]
- struct S {
- data: ByteString,
- }
-
- let input = r#"data 42"#;
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-// ── Deserialization: ValueDeserializer unit ─────────────────────────────
-
-#[test]
-fn value_deserializer_unit_null() {
- let input = r#"
-marker #null
-name "test"
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- marker: (),
- name: String,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, "test");
-}
-
-#[test]
-fn value_deserializer_unit_mismatch() {
- // FieldDeserializer::deserialize_unit always succeeds (calls visit_unit),
- // so unit fields accept any node content. The ValueDeserializer error path
- // for non-null unit is only reachable through direct ValueDeserializer use.
- // Verify that a unit field with a value still works.
- let input = r#"marker 42"#;
- #[derive(Deserialize, Debug)]
- struct S {
- marker: (),
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.marker, ());
-}
-
-// ── Deserialization: ValueDeserializer newtype struct ───────────────────
-
-#[test]
-fn value_deserializer_newtype_struct() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Wrapper(i32);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Wrapper,
- }
-
- let input = r#"val 42"#;
- let v: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(v.val, Wrapper(42));
-}
-
-// ── Deserialization: ValueDeserializer seq/map/struct errors ────────────
-
-#[test]
-fn value_deserializer_seq_error() {
- // Trying to deserialize a scalar as a seq should fail
- let input = r#"items 42"#;
- #[derive(Deserialize)]
- struct S {
- items: Vec<i32>,
- }
- // This actually goes through FieldDeserializer, not ValueDeserializer,
- // but a single arg won't match Vec. Let's force it differently.
- // Actually, single-arg for a seq goes to ArgsSeqAccess with one element.
- // The ValueDeserializer::deserialize_seq path is hit when a scalar KdlValue
- // is used directly. This is hard to hit via the public API since the
- // deserializer layer above handles seq. Let's just verify the error exists.
- let err = serde_kdl::Error::TypeMismatch {
- expected: "sequence",
- got: "scalar value".into(),
- };
- assert!(err.to_string().contains("sequence"));
-}
-
-// ── Deserialization: ValueDeserializer enum from string ─────────────────
-
-#[test]
-fn value_deserializer_enum_non_string() {
- // Integer value can't be an enum variant name
- let input = r#"
-color 42
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- color: Color,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-// ── Deserialization: NodeContentDeserializer paths ──────────────────────
-
-#[test]
-fn node_content_with_children() {
- // Vec of structs exercises MultiNodeSeqAccess → NodeContentDeserializer
- let input = r#"
-server {
- host "a"
- port 1
-}
-server {
- host "b"
- port 2
-}
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- server: Vec<Server>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.server.len(), 2);
-}
-
-#[test]
-fn node_content_tuple_in_seq() {
- // Vec of tuples exercises NodeContentDeserializer::deserialize_tuple
- let input = r#"
-coords 1.0 2.0
-coords 3.0 4.0
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- coords: Vec<(f64, f64)>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.coords, vec![(1.0, 2.0), (3.0, 4.0)]);
-}
-
-#[test]
-fn node_content_enum_in_seq() {
- // Vec of enums exercises NodeContentDeserializer::deserialize_enum
- let input = r#"
-color "Red"
-color "Blue"
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- color: Vec<Color>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.color, vec![Color::Red, Color::Blue]);
-}
-
-#[test]
-fn node_content_option_in_seq() {
- // Tests NodeContentDeserializer::deserialize_option
- let input = r#"
-val 1
-val #null
-val 3
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Vec<Option<i32>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![Some(1), None, Some(3)]);
-}
-
-#[test]
-fn node_content_string_in_seq() {
- // Tests NodeContentDeserializer primitive delegation
- let input = r#"
-name "Alice"
-name "Bob"
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- name: Vec<String>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, vec!["Alice", "Bob"]);
-}
-
-#[test]
-fn node_content_bool_in_seq() {
- let input = r#"
-flag #true
-flag #false
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- flag: Vec<bool>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.flag, vec![true, false]);
-}
-
-#[test]
-fn node_content_int_types_in_seq() {
- let input = r#"
-val 1
-val 2
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S8 {
- val: Vec<i8>,
- }
- let val: S8 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1i8, 2]);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct S16 {
- val: Vec<i16>,
- }
- let val: S16 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1i16, 2]);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct S64 {
- val: Vec<i64>,
- }
- let val: S64 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1i64, 2]);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct U8 {
- val: Vec<u8>,
- }
- let val: U8 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1u8, 2]);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct U16 {
- val: Vec<u16>,
- }
- let val: U16 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1u16, 2]);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct U32 {
- val: Vec<u32>,
- }
- let val: U32 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1u32, 2]);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct U64 {
- val: Vec<u64>,
- }
- let val: U64 = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1u64, 2]);
-}
-
-#[test]
-fn node_content_i128_in_seq() {
- let input = r#"
-val 1
-val 2
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Vec<i128>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1i128, 2]);
-}
-
-#[test]
-fn node_content_u128_in_seq() {
- let input = r#"
-val 1
-val 2
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Vec<u128>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1u128, 2]);
-}
-
-#[test]
-fn node_content_f32_in_seq() {
- let input = r#"
-val 1.5
-val 2.5
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Vec<f32>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1.5f32, 2.5]);
-}
-
-#[test]
-fn node_content_f64_in_seq() {
- let input = r#"
-val 1.5
-val 2.5
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Vec<f64>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec![1.5f64, 2.5]);
-}
-
-#[test]
-fn node_content_char_in_seq() {
- let input = r#"
-ch "A"
-ch "B"
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- ch: Vec<char>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.ch, vec!['A', 'B']);
-}
-
-// ── Deserialization: NodeContentDeserializer struct from properties ─────
-
-#[test]
-fn node_content_struct_from_properties_in_seq() {
- let input = r#"
-point x=1.0 y=2.0
-point x=3.0 y=4.0
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct Point {
- x: f64,
- y: f64,
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- point: Vec<Point>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.point.len(), 2);
- assert_eq!(val.point[0], Point { x: 1.0, y: 2.0 });
- assert_eq!(val.point[1], Point { x: 3.0, y: 4.0 });
-}
-
-// ── Deserialization: NodeContentDeserializer single-arg struct ──────────
-
-#[test]
-fn node_content_single_arg_struct_in_seq() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Wrapper {
- value: i32,
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- item: Vec<Wrapper>,
- }
- let input = r#"
-item 10
-item 20
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.item.len(), 2);
- assert_eq!(val.item[0], Wrapper { value: 10 });
- assert_eq!(val.item[1], Wrapper { value: 20 });
-}
-
-// ── Deserialization: NodeContentDeserializer map from properties ────────
-
-#[test]
-fn node_content_map_from_properties_in_seq() {
- let input = r#"
-entry a="1" b="2"
-entry c="3"
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- entry: Vec<HashMap<String, String>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.entry.len(), 2);
- assert_eq!(val.entry[0].get("a"), Some(&"1".into()));
-}
-
-// ── Deserialization: NodeContentDeserializer complex enum in seq ────────
-
-#[test]
-fn node_content_complex_enum_in_seq() {
- let input = r#"
-shape {
- Circle {
- radius 5.0
- }
-}
-shape {
- Rectangle {
- width 10.0
- height 20.0
- }
-}
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- shape: Vec<Shape>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.shape.len(), 2);
- assert_eq!(val.shape[0], Shape::Circle { radius: 5.0 });
- assert_eq!(
- val.shape[1],
- Shape::Rectangle {
- width: 10.0,
- height: 20.0
- }
- );
-}
-
-// ── Deserialization: NodeContentDeserializer unit ───────────────────────
-
-#[test]
-fn node_content_unit_in_seq() {
- // A node with no args, no children → unit
- let input = r#"
-marker
-marker
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- marker: Vec<()>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.marker, vec![(), ()]);
-}
-
-// ── Deserialization: NodeContentDeserializer newtype struct ─────────────
-
-#[test]
-fn node_content_newtype_in_seq() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Meters(f64);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- dist: Vec<Meters>,
- }
- let input = r#"
-dist 1.0
-dist 2.0
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.dist, vec![Meters(1.0), Meters(2.0)]);
-}
-
-// ── Deserialization: NodeContentDeserializer seq of args ────────────────
-
-#[test]
-fn node_content_multi_arg_as_seq() {
- // A node with multiple args, accessed via NodeContentDeserializer
- // when that node is one element of a repeated-node sequence.
- let input = r#"
-coords 1.0 2.0 3.0
-coords 4.0 5.0 6.0
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- coords: Vec<Vec<f64>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.coords, vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]);
-}
-
-// ── Deserialization: NodeContentDeserializer bytes ──────────────────────
-
-#[test]
-fn node_content_bytes_in_seq() {
- // Exercises NodeContentDeserializer::deserialize_bytes → deserialize_seq
- let input = r#"
-data 1 2 3
-data 4 5 6
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- data: Vec<Vec<u8>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, vec![vec![1u8, 2, 3], vec![4, 5, 6]]);
-}
-
-// ── Deserialization: NodeContentDeserializer dash children ──────────────
-
-#[test]
-fn node_content_dash_children_in_seq() {
- let input = r#"
-group {
- - 1
- - 2
-}
-group {
- - 3
- - 4
-}
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- group: Vec<Vec<i32>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.group, vec![vec![1, 2], vec![3, 4]]);
-}
-
-// ── Deserialization: NodeContentDeserializer tuple struct ───────────────
-
-#[test]
-fn node_content_tuple_struct_in_seq() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Pair(f64, f64);
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- pair: Vec<Pair>,
- }
- let input = r#"
-pair 1.0 2.0
-pair 3.0 4.0
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.pair, vec![Pair(1.0, 2.0), Pair(3.0, 4.0)]);
-}
-
-// ── Deserialization: NodeContentDeserializer enum error ─────────────────
-
-#[test]
-fn node_content_enum_error_in_seq() {
- // A node with only an integer arg can't be deserialized as an enum
- let input = r#"
-color 42
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- color: Vec<Color>,
- }
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
-}
-
-// ── Deserialization: EnumUnitVariantAccess paths ───────────────────────
-
-#[test]
-fn enum_newtype_variant_via_arg() {
- // Newtype variant where the variant name + value are both arguments
- // e.g., `value "Number" 42`
- // This exercises EnumUnitVariantAccess::newtype_variant_seed
- #[derive(Deserialize, Debug, PartialEq)]
- enum Val {
- Number(i64),
- Text(String),
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- value: Val,
- }
- let input = r#"value "Number" 42"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.value, Val::Number(42));
-}
-
-#[test]
-fn enum_tuple_variant_via_args() {
- // Tuple variant where variant name + tuple elements are all arguments
- // This exercises EnumUnitVariantAccess::tuple_variant
- #[derive(Deserialize, Debug, PartialEq)]
- enum Val {
- Point(f64, f64),
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- value: Val,
- }
- let input = r#"value "Point" 1.0 2.0"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.value, Val::Point(1.0, 2.0));
-}
-
-#[test]
-fn enum_struct_variant_via_props() {
- // Struct variant where variant name is a string arg and fields are properties
- // This exercises EnumUnitVariantAccess::struct_variant
- #[derive(Deserialize, Debug, PartialEq)]
- enum Val {
- Circle { radius: f64 },
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- value: Val,
- }
- let input = r#"value "Circle" radius=5.0"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.value, Val::Circle { radius: 5.0 });
-}
-
-// ── Deserialization: EnumComplexAccess additional paths ─────────────────
-
-#[test]
-fn enum_complex_unit_variant() {
- // Unit variant via child node (no args, no children)
- // This exercises EnumComplexVariantAccess::unit_variant
- #[derive(Deserialize, Debug, PartialEq)]
- enum Status {
- Active,
- Inactive,
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- status: Status,
- }
- // The node has a child node with the variant name and no further content
- let input = r#"
-status {
- Active
-}
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.status, Status::Active);
-}
-
-#[test]
-fn enum_complex_tuple_variant() {
- // Tuple variant via child node with multiple args
- // This exercises EnumComplexVariantAccess::tuple_variant
- #[derive(Deserialize, Debug, PartialEq)]
- enum Val {
- Point(f64, f64, f64),
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- data: Val,
- }
- let input = r#"
-data {
- Point 1.0 2.0 3.0
-}
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, Val::Point(1.0, 2.0, 3.0));
-}
-
-#[test]
-fn enum_complex_struct_variant_from_props() {
- // Struct variant where variant node uses properties (no children block)
- // This exercises EnumComplexVariantAccess::struct_variant → PropsMapAccess
- #[derive(Deserialize, Debug, PartialEq)]
- enum Val {
- Circle { radius: f64 },
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- shape: Val,
- }
- let input = r#"
-shape {
- Circle radius=5.0
-}
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.shape, Val::Circle { radius: 5.0 });
-}
-
-// ── Deserialization: DocumentDeserializer extra paths ───────────────────
-
-#[test]
-fn document_deserialize_newtype_struct() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Inner {
- name: String,
- }
- #[derive(Deserialize, Debug, PartialEq)]
- struct Outer(Inner);
-
- let input = r#"name "test""#;
- let val: Outer = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.0.name, "test");
-}
-
-#[test]
-fn document_deserialize_unit() {
- // An empty document can be deserialized as unit
- let input = r#""#;
- let _: () = serde_kdl::from_str(input).unwrap();
-}
-
-#[test]
-fn document_deserialize_unit_struct() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Empty;
- let input = r#""#;
- let _: Empty = serde_kdl::from_str(input).unwrap();
-}
-
-// ── Serialization: Null field via explicit null ─────────────────────────
-
#[test]
fn serialize_explicit_null_value() {
- // Option<T> = None serializes as Null, which is skipped.
- // But we can test the Null arm of value_to_nodes by serializing
- // a struct where a field produces Null through the serializer.
#[derive(Serialize)]
struct S {
- // UnitStruct serializes as Null via serialize_unit_struct
marker: UnitStruct,
}
- let val = S { marker: UnitStruct };
- // UnitStruct → Null → skipped by SerializeStruct (same as None)
- let output = serde_kdl::to_string(&val).unwrap();
- // The marker field should be omitted (Null is skipped)
+ let output = serde_kdl::to_string(&S { marker: UnitStruct }).unwrap();
assert!(!output.contains("marker"));
}
-// ── Serialization: f32 field ───────────────────────────────────────────
-
#[test]
fn serialize_f32_field() {
- let val = WithF32 { value: 2.5 };
- let output = serde_kdl::to_string(&val).unwrap();
+ let output = serde_kdl::to_string(&W_f32 { value: 2.5 }).unwrap();
assert!(output.contains("2.5"));
}
-// ── FieldDeserializer: deserialize_bytes delegates to seq ──────────────
-
#[test]
-fn field_deserializer_bytes_as_seq() {
- // A node with multiple integer args deserialized as bytes
- let input = r#"data 72 101 108"#;
- #[derive(Deserialize, Debug)]
+fn serialize_map_integer_keys() {
+ #[derive(Debug, Serialize)]
struct S {
- data: Vec<u8>,
+ lookup: HashMap<i32, String>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, vec![72, 101, 108]);
+ let mut lookup = HashMap::new();
+ lookup.insert(1, "one".into());
+ let output = serde_kdl::to_string(&S { lookup }).unwrap();
+ assert!(output.contains("1"));
}
-// ── FieldDeserializer: identifier delegates to str ─────────────────────
-
#[test]
-fn deserialize_identifier_field() {
- // serde uses deserialize_identifier for enum variant names and map keys.
- // Already covered by enum tests, but verify an extra path.
- let input = r#"
-name "widget"
-color "Blue"
-"#;
- let val: Colored = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.color, Color::Blue);
+fn serialize_map_bool_keys() {
+ #[derive(Debug, Serialize)]
+ struct S {
+ flags: HashMap<bool, String>,
+ }
+ let mut flags = HashMap::new();
+ flags.insert(true, "yes".into());
+ let output = serde_kdl::to_string(&S { flags }).unwrap();
+ assert!(output.contains("true"));
}
-// ── FieldDeserializer: ignored_any ─────────────────────────────────────
-
#[test]
-fn deserialize_with_extra_fields() {
- // Extra fields in KDL should be ignored when struct doesn't have them
- let input = r#"
-name "test"
-extra "ignored"
-another 42
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
+fn serialize_mixed_sequence() {
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
+ #[serde(untagged)]
+ enum V {
+ Num(i64),
+ Str(String),
+ }
+ #[derive(Debug, Serialize)]
struct S {
- name: String,
+ items: Vec<V>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, "test");
+ let output = serde_kdl::to_string(&S {
+ items: vec![V::Num(1), V::Num(2)],
+ })
+ .unwrap();
+ assert!(output.contains("items"));
}
-// ── NodeContentDeserializer: ignored_any ───────────────────────────────
-
#[test]
-fn node_content_ignored_any() {
- // A struct inside a seq with extra fields
- let input = r#"
-item {
- name "test"
- extra "ignored"
+fn serialize_mixed_primitive_sequence() {
+ #[derive(Serialize, Debug)]
+ #[serde(untagged)]
+ enum Mixed {
+ Int(i32),
+ Str(String),
+ }
+ #[derive(Serialize, Debug)]
+ struct S {
+ items: Vec<Mixed>,
+ }
+ let output = serde_kdl::to_string(&S {
+ items: vec![Mixed::Int(1), Mixed::Str("two".into()), Mixed::Int(3)],
+ })
+ .unwrap();
+ assert!(output.contains("items"));
}
-item {
- name "test2"
- bonus 99
+
+#[test]
+fn serialize_nested_sequence() {
+ #[derive(Serialize, Debug)]
+ struct S {
+ matrix: Vec<Vec<i32>>,
+ }
+ let output = serde_kdl::to_string(&S {
+ matrix: vec![vec![1, 2], vec![3, 4]],
+ })
+ .unwrap();
+ assert!(output.contains("-"));
}
-"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct Item {
- name: String,
+
+#[test]
+fn serialize_vec_bools() {
+ #[derive(Serialize, Deserialize, Debug, PartialEq)]
+ struct S {
+ flags: Vec<bool>,
}
- #[derive(Deserialize, Debug, PartialEq)]
+ let val = S {
+ flags: vec![true, false, true],
+ };
+ let output = serde_kdl::to_string(&val).unwrap();
+ let rt: S = serde_kdl::from_str(&output).unwrap();
+ assert_eq!(val, rt);
+}
+
+#[test]
+fn serialize_vec_option_with_nulls() {
+ #[derive(Serialize, Debug)]
struct S {
- item: Vec<Item>,
+ vals: Vec<Option<i32>>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.item.len(), 2);
- assert_eq!(val.item[0].name, "test");
+ let output = serde_kdl::to_string(&S {
+ vals: vec![Some(1), None, Some(3)],
+ })
+ .unwrap();
+ assert!(output.contains("#null"));
}
-// ── Serialization: Null value in value_to_nodes ────────────────────────
+#[test]
+fn serialize_mixed_seq_with_null() {
+ #[derive(Serialize, Debug)]
+ struct S {
+ items: Vec<Option<Vec<i32>>>,
+ }
+ let output = serde_kdl::to_string(&S {
+ items: vec![Some(vec![1, 2]), None, Some(vec![3])],
+ })
+ .unwrap();
+ assert!(output.contains("#null"));
+}
#[test]
fn serialize_option_some_null_nested() {
- // Option<Option<T>> where inner is None produces a Null that goes
- // through value_to_nodes. But the SerializeStruct impl skips Null.
- // We need a map-based serialization to exercise it.
- // Use a HashMap<String, Option<String>> where a value is None.
#[derive(Serialize)]
struct S {
items: HashMap<String, Option<String>>,
@@ -2043,255 +898,425 @@ fn serialize_option_some_null_nested() {
let mut items = HashMap::new();
items.insert("present".into(), Some("value".into()));
items.insert("absent".into(), None);
- let val = S { items };
- let output = serde_kdl::to_string(&val).unwrap();
- // "absent" should be skipped (None → Null → skipped by SerializeMap)
+ let output = serde_kdl::to_string(&S { items }).unwrap();
assert!(!output.contains("absent"));
assert!(output.contains("present"));
}
-// ── ValueDeserializer: deserialize_any for all KDL types ───────────────
+#[test]
+fn serialize_unsupported_map_key() {
+ let err = serde_kdl::Error::Unsupported("map key must be a string, got Null".into());
+ assert!(err.to_string().contains("map key"));
+}
+
+// ════════════════════════════════════════════════════════════════════════
+// Error trait tests
+// ════════════════════════════════════════════════════════════════════════
+
+#[test]
+fn error_display_variants() {
+ assert_eq!(
+ serde_kdl::Error::TopLevelNotStruct.to_string(),
+ "top-level type must be a struct or map"
+ );
+ assert_eq!(
+ serde_kdl::Error::Message("custom error".into()).to_string(),
+ "custom error"
+ );
+ assert!(
+ serde_kdl::Error::TypeMismatch {
+ expected: "string",
+ got: "integer".into()
+ }
+ .to_string()
+ .contains("expected string")
+ );
+ assert!(
+ serde_kdl::Error::MissingField("name".into())
+ .to_string()
+ .contains("name")
+ );
+ assert!(
+ serde_kdl::Error::IntegerOutOfRange(999999)
+ .to_string()
+ .contains("999999")
+ );
+ assert!(
+ serde_kdl::Error::UnknownVariant("Foo".into())
+ .to_string()
+ .contains("Foo")
+ );
+ assert!(
+ serde_kdl::Error::Unsupported("nope".into())
+ .to_string()
+ .contains("nope")
+ );
+}
+
+#[test]
+fn serde_error_custom_impls() {
+ assert_eq!(
+ <serde_kdl::Error as serde::de::Error>::custom("deser fail").to_string(),
+ "deser fail"
+ );
+ assert_eq!(
+ <serde_kdl::Error as serde::ser::Error>::custom("ser fail").to_string(),
+ "ser fail"
+ );
+}
+
+// ════════════════════════════════════════════════════════════════════════
+// Deserialization error tests — data-driven
+// ════════════════════════════════════════════════════════════════════════
+
+deser_err!(deserialize_invalid_kdl, SimpleConfig, "{{{{invalid");
+deser_field_err!(deserialize_bool_type_mismatch, flag: bool, r#"flag "not a bool""#);
+deser_field_err!(deserialize_string_type_mismatch, name: String, "name 42");
+deser_field_err!(deserialize_int_type_mismatch, value: i32, r#"value "not a number""#);
+deser_field_err!(deserialize_float_type_mismatch, value: f64, r#"value "not a float""#);
+deser_field_err!(deserialize_char_type_mismatch, ch: char, r#"ch "abc""#);
+deser_field_err!(deserialize_char_from_int_mismatch, ch: char, "ch 65");
+deser_field_err!(deserialize_integer_overflow, value: i8, "value 999");
+deser_field_err!(deserialize_node_no_args, value: String, "value");
+deser_err!(deserialize_u128_overflow, W_u128, "value -1");
+deser_field_err!(deserialize_enum_no_match, color: Color, "color 42");
+deser_field_err!(deserialize_enum_non_string, color: Color, "color 42");
+deser_repeated_vec_err!(node_content_enum_error_in_seq, color: Vec<Color>, "color 42\n");
+deser_field_err!(value_deserializer_unit_mismatch_in_args_err, vals: Vec<()>, "vals 42");
#[test]
-fn value_deserializer_any_integer() {
- // When a struct field is untyped (serde_value or similar),
- // deserialize_any on an integer should work.
- let input = r#"val 42"#;
+fn field_deserialize_enum_multi_children_error() {
#[derive(Deserialize, Debug)]
struct S {
- val: i128,
+ #[allow(dead_code)]
+ shape: Shape,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, 42);
+ assert!(serde_kdl::from_str::<S>("shape {\n Circle {\n radius 5.0\n }\n Rectangle {\n width 10.0\n }\n}\n").is_err());
}
+// ════════════════════════════════════════════════════════════════════════
+// Deserialization: single-field tests — data-driven
+// ════════════════════════════════════════════════════════════════════════
+
+deser_field!(deserialize_f32_from_integer, value: f32, "value 3", 3.0f32);
+deser_field!(deserialize_f64_from_integer, value: f64, "value 42", 42.0f64);
+deser_field!(deserialize_int_from_float, count: i32, "count 3.0", 3);
+deser_field!(value_deserializer_any_integer, val: i128, "val 42", 42i128);
+deser_field!(value_deserializer_any_bool, val: bool, "val #true", true);
+deser_field!(value_deserializer_null_option, optional: Option<i32>, "optional #null", None);
+deser_field!(value_deserializer_any_null, val: Option<String>, "val #null", None);
+
#[test]
fn value_deserializer_any_float() {
- let input = r#"val 3.14"#;
#[derive(Deserialize, Debug)]
struct S {
val: f64,
}
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str("val 3.14").unwrap();
assert!((val.val - 3.14).abs() < 0.001);
}
#[test]
-fn value_deserializer_any_bool() {
- let input = r#"val #true"#;
+fn value_deserializer_unit_null() {
#[derive(Deserialize, Debug)]
struct S {
- val: bool,
+ marker: (),
+ name: String,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert!(val.val);
+ let val: S = serde_kdl::from_str("marker #null\nname \"test\"\n").unwrap();
+ assert_eq!(val.name, "test");
}
#[test]
-fn value_deserializer_any_null() {
- let input = r#"val #null"#;
+fn value_deserializer_unit_mismatch() {
#[derive(Deserialize, Debug)]
struct S {
- val: Option<String>,
+ marker: (),
+ }
+ let val: S = serde_kdl::from_str("marker 42").unwrap();
+ assert_eq!(val.marker, ());
+}
+
+deser_field!(value_deserializer_newtype_struct, val: (i32,), "val 42", (42,));
+
+// Actually, newtype struct requires a named wrapper:
+#[test]
+fn value_deserializer_newtype_struct_named() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct W(i32);
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ val: W,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, None);
+ let val: S = serde_kdl::from_str("val 42").unwrap();
+ assert_eq!(val.val, W(42));
}
// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: DocumentDeserializer paths
+// FieldDeserializer::deserialize_any branches — data-driven via untagged
// ════════════════════════════════════════════════════════════════════════
#[test]
-fn document_deserialize_any_as_map() {
- // Use #[serde(untagged)] at the top level to exercise DocumentDeserializer::deserialize_any.
- // Stick to string fields since serde's Content intermediary doesn't coerce i128→i32.
- #[derive(Deserialize, Debug, PartialEq)]
- #[serde(untagged)]
- enum TopLevel {
- Config { name: String, label: String },
+fn deserialize_any_with_properties() {
+ #[derive(Deserialize, Debug)]
+ struct S {
+ item: HashMap<String, String>,
}
- let input = r#"
-name "test"
-label "hello"
-"#;
- let val: TopLevel = serde_kdl::from_str(input).unwrap();
- assert_eq!(
- val,
- TopLevel::Config {
- name: "test".into(),
- label: "hello".into()
- }
- );
+ let val: S = serde_kdl::from_str(r#"item key="value""#).unwrap();
+ assert_eq!(val.item.get("key"), Some(&"value".into()));
}
+deser_field!(deserialize_any_with_multiple_args, values: Vec<i32>, "values 1 2 3", vec![1, 2, 3]);
+
#[test]
-fn document_deserialize_ignored_any() {
- // #[serde(deny_unknown_fields)] is the opposite; instead, use a struct
- // that ignores fields. The DocumentMapAccess sends unknown fields through
- // deserialize_ignored_any on FieldDeserializer. But DocumentDeserializer's
- // own deserialize_ignored_any is only called if the top-level deserializer
- // itself is asked to ignore. This is hard to trigger directly.
- // Let's just verify that extra fields don't cause errors with a basic struct.
- let input = r#"
-name "test"
-unknown "ignored"
-"#;
+fn deserialize_any_unit_node() {
#[derive(Deserialize, Debug)]
struct S {
+ marker: (),
name: String,
}
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str("marker\nname \"test\"\n").unwrap();
assert_eq!(val.name, "test");
}
-// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: FieldDeserializer::deserialize_any branches
-// ════════════════════════════════════════════════════════════════════════
+// ── untagged enum branches ─────────────────────────────────────────────
+
+macro_rules! test_untagged_any {
+ ($name:ident, $variant_ty:ty, $input:expr, $check:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ #[serde(untagged)]
+ enum DynVal {
+ V($variant_ty),
+ }
+ #[derive(Deserialize, Debug)]
+ struct S {
+ data: DynVal,
+ }
+ let val: S = serde_kdl::from_str($input).unwrap();
+ let DynVal::V(inner) = val.data;
+ let check: $variant_ty = $check;
+ assert_eq!(inner, check);
+ }
+ };
+}
+
+test_untagged_any!(
+ field_deserialize_any_single_arg,
+ String,
+ r#"data "hello""#,
+ String::from("hello")
+);
+test_untagged_any!(
+ field_deserialize_any_multi_arg,
+ Vec<String>,
+ r#"data "a" "b" "c""#,
+ vec!["a".into(), "b".into(), "c".into()]
+);
+test_untagged_any!(field_deserialize_any_children, HashMap<String, String>, "data {\n key \"value\"\n}\n", { let mut m = HashMap::new(); m.insert("key".into(), "value".into()); m });
+test_untagged_any!(field_deserialize_any_props, HashMap<String, String>, r#"data key="value""#, { let mut m = HashMap::new(); m.insert("key".into(), "value".into()); m });
#[test]
-fn field_deserialize_any_children() {
- // Node with children → deserialize_any → deserialize_map.
- // Use #[serde(untagged)] with string-only fields to avoid i128 coercion issues.
+fn field_deserialize_any_no_args() {
#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
enum DynVal {
- Map(HashMap<String, String>),
+ Unit,
}
- #[derive(Deserialize, Debug, PartialEq)]
+ #[derive(Deserialize, Debug)]
struct S {
data: DynVal,
}
- let input = r#"
-data {
- key "value"
-}
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- match val.data {
- DynVal::Map(m) => assert_eq!(m.get("key"), Some(&"value".into())),
- }
+ let val: S = serde_kdl::from_str("data").unwrap();
+ assert_eq!(val.data, DynVal::Unit);
}
#[test]
-fn field_deserialize_any_props() {
- // Node with properties → deserialize_any → deserialize_map
- #[derive(Deserialize, Debug, PartialEq)]
+fn field_deserialize_any_float() {
+ #[derive(Deserialize, Debug)]
#[serde(untagged)]
enum DynVal {
- Map(HashMap<String, String>),
+ Float(f64),
}
- #[derive(Deserialize, Debug, PartialEq)]
+ #[derive(Deserialize, Debug)]
struct S {
data: DynVal,
}
- let input = r#"data key="value""#;
- let val: S = serde_kdl::from_str(input).unwrap();
- match val.data {
- DynVal::Map(m) => assert_eq!(m.get("key"), Some(&"value".into())),
- }
+ let val: S = serde_kdl::from_str("data 3.14").unwrap();
+ let DynVal::Float(f) = val.data;
+ assert!((f - 3.14).abs() < 0.001);
}
#[test]
-fn field_deserialize_any_multi_arg() {
- // Node with multiple args → deserialize_any → deserialize_seq.
- // Use string elements to avoid i128 coercion in serde's Content.
- #[derive(Deserialize, Debug, PartialEq)]
+fn field_deserialize_any_bool() {
+ #[derive(Deserialize, Debug)]
#[serde(untagged)]
enum DynVal {
- Seq(Vec<String>),
+ Bool(bool),
}
#[derive(Deserialize, Debug)]
struct S {
data: DynVal,
}
- let input = r#"data "a" "b" "c""#;
- let val: S = serde_kdl::from_str(input).unwrap();
- match val.data {
- DynVal::Seq(v) => assert_eq!(v, vec!["a", "b", "c"]),
- }
+ let val: S = serde_kdl::from_str("data #true").unwrap();
+ let DynVal::Bool(b) = val.data;
+ assert!(b);
}
#[test]
-fn field_deserialize_any_single_arg() {
- // Node with single arg → deserialize_any → ValueDeserializer::deserialize_any
+fn field_deserialize_any_null() {
#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
enum DynVal {
+ Nothing,
Str(String),
}
#[derive(Deserialize, Debug)]
struct S {
- data: DynVal,
+ data: Option<DynVal>,
+ }
+ let val: S = serde_kdl::from_str("data #null").unwrap();
+ assert_eq!(val.data, None);
+}
+
+#[test]
+fn field_deserialize_any_integer_limitation() {
+ #[derive(Deserialize, Debug)]
+ #[serde(untagged)]
+ enum DynVal {
+ #[allow(dead_code)]
+ Num(i64),
}
- let input = r#"data "hello""#;
- let val: S = serde_kdl::from_str(input).unwrap();
- match val.data {
- DynVal::Str(s) => assert_eq!(s, "hello"),
+ #[derive(Deserialize, Debug)]
+ struct S {
+ #[allow(dead_code)]
+ data: DynVal,
}
+ assert!(serde_kdl::from_str::<S>("data 42").is_err());
}
+// ════════════════════════════════════════════════════════════════════════
+// DocumentDeserializer extra paths
+// ════════════════════════════════════════════════════════════════════════
+
#[test]
-fn field_deserialize_any_no_args() {
- // Node with no args, no children, no props → deserialize_any → visit_unit
+fn document_deserialize_any_as_map() {
#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
- enum DynVal {
- Unit,
+ enum TopLevel {
+ Config { name: String, label: String },
+ }
+ let val: TopLevel = serde_kdl::from_str("name \"test\"\nlabel \"hello\"\n").unwrap();
+ assert_eq!(
+ val,
+ TopLevel::Config {
+ name: String::from("test"),
+ label: String::from("hello")
+ }
+ );
+}
+
+#[test]
+fn document_deserialize_unit() {
+ let _: () = serde_kdl::from_str("").unwrap();
+}
+
+#[test]
+fn document_deserialize_unit_struct() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Empty;
+ let _: Empty = serde_kdl::from_str("").unwrap();
+}
+
+#[test]
+fn document_deserialize_newtype_struct() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Inner {
+ name: String,
+ }
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Outer(Inner);
+ let val: Outer = serde_kdl::from_str("name \"test\"").unwrap();
+ assert_eq!(val.0.name, "test");
+}
+
+// ── Extra field handling (ignored_any) ─────────────────────────────────
+
+deser_field!(deserialize_with_extra_fields, name: String, "name \"test\"\nextra \"ignored\"\nanother 42\n", String::from("test"));
+deser_field!(document_deserialize_ignored_any, name: String, "name \"test\"\nunknown \"ignored\"\n", String::from("test"));
+deser_field!(field_ignored_any_with_children, name: String, "name \"test\"\ncomplex {\n nested \"value\"\n deep {\n x 1\n }\n}\n", String::from("test"));
+
+// ════════════════════════════════════════════════════════════════════════
+// Properties-based struct/map deserialization — data-driven
+// ════════════════════════════════════════════════════════════════════════
+
+#[test]
+fn deserialize_struct_from_properties() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Point {
+ x: f64,
+ y: f64,
+ }
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ point: Point,
}
+ let val: S = serde_kdl::from_str(r#"point x=1.0 y=2.0"#).unwrap();
+ assert_eq!(val.point, Point { x: 1.0, y: 2.0 });
+}
+
+#[test]
+fn deserialize_map_from_properties() {
#[derive(Deserialize, Debug)]
struct S {
- data: DynVal,
+ meta: HashMap<String, String>,
}
- let input = r#"data"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, DynVal::Unit);
+ let val: S = serde_kdl::from_str(r#"meta author="Alice" version="1.0""#).unwrap();
+ assert_eq!(val.meta.get("author"), Some(&String::from("Alice")));
+ assert_eq!(val.meta.get("version"), Some(&"1.0".into()));
}
+deser_field!(deserialize_empty_map_from_node, meta: HashMap<String, String>, "meta", HashMap::new());
+
+// ── Non-dash children as sequence ──────────────────────────────────────
+
+deser_field!(deserialize_children_as_sequence, items: Vec<i32>, "items {\n item 1\n item 2\n item 3\n}\n", vec![1, 2, 3]);
+
// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: FieldDeserializer misc paths
+// FieldDeserializer misc paths — data-driven
// ════════════════════════════════════════════════════════════════════════
#[test]
fn field_deserialize_unit_struct() {
- // Named unit struct as a field
#[derive(Deserialize, Debug, PartialEq)]
struct Marker;
#[derive(Deserialize, Debug, PartialEq)]
struct S {
tag: Marker,
}
- let input = r#"tag"#;
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str("tag").unwrap();
assert_eq!(val.tag, Marker);
}
#[test]
fn field_deserialize_newtype_struct() {
- // Newtype struct wrapping another struct, deserialized from a node with children
#[derive(Deserialize, Debug, PartialEq)]
struct Inner {
x: i32,
}
#[derive(Deserialize, Debug, PartialEq)]
- struct Wrapper(Inner);
+ struct W(Inner);
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- data: Wrapper,
+ data: W,
}
- let input = r#"
-data {
- x 42
-}
-"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, Wrapper(Inner { x: 42 }));
+ let val: S = serde_kdl::from_str("data {\n x 42\n}\n").unwrap();
+ assert_eq!(val.data, W(Inner { x: 42 }));
}
#[test]
fn field_deserialize_struct_from_properties() {
- // struct fields from node properties, specifically exercising the
- // FieldDeserializer::deserialize_struct → PropsMapAccess path
#[derive(Deserialize, Debug, PartialEq)]
struct Point {
x: f64,
@@ -2301,30 +1326,26 @@ fn field_deserialize_struct_from_properties() {
struct S {
pos: Point,
}
- let input = r#"pos x=1.0 y=2.0"#;
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str("pos x=1.0 y=2.0").unwrap();
assert_eq!(val.pos, Point { x: 1.0, y: 2.0 });
}
#[test]
fn field_deserialize_struct_single_arg() {
- // Struct with a single field from a single-arg node
#[derive(Deserialize, Debug, PartialEq)]
- struct Wrapper {
+ struct W {
value: i32,
}
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- item: Wrapper,
+ item: W,
}
- let input = r#"item 42"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.item, Wrapper { value: 42 });
+ let val: S = serde_kdl::from_str("item 42").unwrap();
+ assert_eq!(val.item, W { value: 42 });
}
#[test]
fn field_deserialize_struct_empty() {
- // Struct with no matching fields from a node with no content
#[derive(Deserialize, Debug, PartialEq, Default)]
struct Empty {
#[serde(default)]
@@ -2336,506 +1357,260 @@ fn field_deserialize_struct_empty() {
struct S {
data: Empty,
}
- let input = r#"data"#;
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str("data").unwrap();
assert_eq!(val.data, Empty { a: None, b: None });
}
-// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: ValueDeserializer methods via ArgsSeqAccess
-// ════════════════════════════════════════════════════════════════════════
-
-#[test]
-fn args_seq_option_with_null() {
- // Multi-arg node where one arg is null → ValueDeserializer::deserialize_option
- let input = r#"vals "hello" #null "world""#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- vals: Vec<Option<String>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(
- val.vals,
- vec![Some("hello".into()), None, Some("world".into())]
- );
-}
-
-#[test]
-fn args_seq_bool_values() {
- // Multi-arg bools → ValueDeserializer::deserialize_bool
- let input = r#"flags #true #false #true"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- flags: Vec<bool>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.flags, vec![true, false, true]);
-}
+deser_field!(field_deserializer_bytes_as_seq, data: Vec<u8>, "data 72 101 108", vec![72u8, 101, 108]);
-#[test]
-fn args_seq_string_values() {
- // Multi-arg strings → ValueDeserializer::deserialize_str
- let input = r#"names "Alice" "Bob""#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- names: Vec<String>,
+deser_ok!(
+ deserialize_identifier_field,
+ Colored,
+ "name \"widget\"\ncolor \"Blue\"\n",
+ Colored {
+ name: "widget".into(),
+ color: Color::Blue
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.names, vec!["Alice", "Bob"]);
-}
+);
-#[test]
-fn args_seq_enum_values() {
- // Multi-arg enum unit variants → ValueDeserializer::deserialize_enum
- let input = r#"colors "Red" "Blue" "Green""#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- colors: Vec<Color>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.colors, vec![Color::Red, Color::Blue, Color::Green]);
-}
+// ════════════════════════════════════════════════════════════════════════
+// Enum access paths — data-driven
+// ════════════════════════════════════════════════════════════════════════
#[test]
-fn args_seq_char_values() {
- // Multi-arg chars → ValueDeserializer::deserialize_char
- let input = r#"letters "A" "B" "C""#;
+fn enum_newtype_variant_via_arg() {
#[derive(Deserialize, Debug, PartialEq)]
- struct S {
- letters: Vec<char>,
+ enum Val {
+ Number(i64),
+ Text(String),
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.letters, vec!['A', 'B', 'C']);
-}
-
-#[test]
-fn args_seq_i128_values() {
- let input = r#"vals 100 200 300"#;
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- vals: Vec<i128>,
+ value: Val,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![100i128, 200, 300]);
+ let val: S = serde_kdl::from_str(r#"value "Number" 42"#).unwrap();
+ assert_eq!(val.value, Val::Number(42));
}
#[test]
-fn args_seq_u128_values() {
- let input = r#"vals 100 200 300"#;
+fn enum_tuple_variant_via_args() {
#[derive(Deserialize, Debug, PartialEq)]
- struct S {
- vals: Vec<u128>,
+ enum Val {
+ Point(f64, f64),
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![100u128, 200, 300]);
-}
-
-#[test]
-fn args_seq_f32_values() {
- let input = r#"vals 1.5 2.5 3.5"#;
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- vals: Vec<f32>,
+ value: Val,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![1.5f32, 2.5, 3.5]);
+ let val: S = serde_kdl::from_str(r#"value "Point" 1.0 2.0"#).unwrap();
+ assert_eq!(val.value, Val::Point(1.0, 2.0));
}
#[test]
-fn args_seq_f64_values() {
- let input = r#"vals 1.5 2.5 3.5"#;
+fn enum_struct_variant_via_props() {
#[derive(Deserialize, Debug, PartialEq)]
- struct S {
- vals: Vec<f64>,
+ enum Val {
+ Circle { radius: f64 },
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![1.5f64, 2.5, 3.5]);
-}
-
-#[test]
-fn args_seq_newtype_values() {
- // Multi-arg newtype structs → ValueDeserializer::deserialize_newtype_struct
- #[derive(Deserialize, Debug, PartialEq)]
- struct Meters(f64);
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- dists: Vec<Meters>,
+ value: Val,
}
- let input = r#"dists 1.0 2.0 3.0"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.dists, vec![Meters(1.0), Meters(2.0), Meters(3.0)]);
+ let val: S = serde_kdl::from_str(r#"value "Circle" radius=5.0"#).unwrap();
+ assert_eq!(val.value, Val::Circle { radius: 5.0 });
}
-// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: NodeContentDeserializer::deserialize_any branches
-// ════════════════════════════════════════════════════════════════════════
-
-// NodeContentDeserializer::deserialize_any branches are structurally identical
-// to FieldDeserializer::deserialize_any (tested above) and are exercised
-// through typed paths in the node_content_*_in_seq tests. Serde's untagged
-// enum Content buffering can't round-trip through tree-structured deserializers,
-// so we can't trigger deserialize_any on NodeContentDeserializer via the
-// public API. These branches are marked with cov-excl in the source.
-
-// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: NodeContentDeserializer remaining methods
-// ════════════════════════════════════════════════════════════════════════
-
#[test]
-fn node_content_string_in_repeated_nodes() {
- // Exercises NodeContentDeserializer::deserialize_string (not str)
- let input = r#"
-val "hello"
-val "world"
-"#;
+fn enum_complex_unit_variant() {
#[derive(Deserialize, Debug, PartialEq)]
- struct S {
- val: Vec<String>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.val, vec!["hello", "world"]);
-}
-
-#[test]
-fn node_content_map_from_children() {
- // Exercises NodeContentDeserializer::deserialize_map with children
- let input = r#"
-entry {
- a "1"
- b "2"
-}
-entry {
- c "3"
-}
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- entry: Vec<HashMap<String, String>>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.entry[0].get("a"), Some(&"1".into()));
- assert_eq!(val.entry[1].get("c"), Some(&"3".into()));
-}
-
-#[test]
-fn node_content_map_from_props_no_children() {
- // Exercises NodeContentDeserializer::deserialize_map without children (props fallback)
- let input = r#"
-entry a="1" b="2"
-entry c="3"
-"#;
- #[derive(Deserialize, Debug)]
- struct S {
- entry: Vec<HashMap<String, String>>,
+ enum Status {
+ Active,
+ Inactive,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.entry[0].get("a"), Some(&"1".into()));
- assert_eq!(val.entry[1].get("c"), Some(&"3".into()));
-}
-
-#[test]
-fn node_content_non_dash_children_seq() {
- // Exercises NodeContentDeserializer::deserialize_seq with non-dash children
- let input = r#"
-group {
- item 1
- item 2
-}
-group {
- item 3
-}
-"#;
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- group: Vec<Vec<i32>>,
+ status: Status,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.group, vec![vec![1, 2], vec![3]]);
+ let val: S = serde_kdl::from_str("status {\n Active\n}\n").unwrap();
+ assert_eq!(val.status, Status::Active);
}
#[test]
-fn node_content_identifier_in_enum_seq() {
- // Exercises NodeContentDeserializer::deserialize_identifier (via enum deser)
- let input = r#"
-color "Red"
-color "Green"
-"#;
+fn enum_complex_tuple_variant() {
#[derive(Deserialize, Debug, PartialEq)]
- struct S {
- color: Vec<Color>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.color, vec![Color::Red, Color::Green]);
-}
-
-// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: serialization paths
-// ════════════════════════════════════════════════════════════════════════
-
-#[test]
-fn serialize_mixed_primitive_sequence() {
- // A sequence containing mixed types (via untagged enum) that all serialize
- // as primitives: exercises the `all_primitive` branch in value_to_nodes.
- #[derive(Serialize, Deserialize, Debug, PartialEq)]
- #[serde(untagged)]
- enum Mixed {
- Int(i32),
- Str(String),
- }
- #[derive(Serialize, Debug)]
- struct S {
- items: Vec<Mixed>,
- }
- let val = S {
- items: vec![Mixed::Int(1), Mixed::Str("two".into()), Mixed::Int(3)],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("items"));
-}
-
-#[test]
-fn serialize_nested_sequence() {
- // A sequence of sequences → mixed (inner is Seq, not primitive/map)
- // Exercises the "mixed or nested sequences → use `-` children" branch
- #[derive(Serialize, Debug)]
- struct S {
- matrix: Vec<Vec<i32>>,
- }
- let val = S {
- matrix: vec![vec![1, 2], vec![3, 4]],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- // Should use `-` children convention
- assert!(output.contains("matrix"));
- assert!(output.contains("-"));
-}
-
-#[test]
-fn serialize_unsupported_map_key() {
- // A map with a float key → unsupported
- #[derive(Serialize, Debug)]
- struct S {
- data: HashMap<f64, String>,
- }
- // f64 doesn't implement Hash, so let's use a custom serializer
- // Actually, we can test this indirectly. The error path in ser.rs
- // fires when a map key serializes to something other than String/Integer/Bool.
- // Float keys would trigger it, but HashMap<f64, _> won't compile.
- // The path is for Null/Seq/Map keys. These are rare in practice.
- // Just verify the error variant exists.
- let err = serde_kdl::Error::Unsupported("map key must be a string, got Null".into());
- assert!(err.to_string().contains("map key"));
-}
-
-#[test]
-fn serialize_vec_bools() {
- // Exercises to_kdl_value Bool branch via all_primitive sequence path
- #[derive(Serialize, Deserialize, Debug, PartialEq)]
- struct S {
- flags: Vec<bool>,
- }
- let val = S {
- flags: vec![true, false, true],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- let roundtrip: S = serde_kdl::from_str(&output).unwrap();
- assert_eq!(val, roundtrip);
-}
-
-#[test]
-fn serialize_vec_option_with_nulls() {
- // Exercises to_kdl_value Null branch: None serializes as Value::Null,
- // is_primitive() is true for Null, so it reaches to_kdl_value.
- #[derive(Serialize, Debug, PartialEq)]
- struct S {
- vals: Vec<Option<i32>>,
- }
- let val = S {
- vals: vec![Some(1), None, Some(3)],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("vals"));
- assert!(output.contains("#null"));
-}
-
-#[test]
-fn serialize_mixed_seq_with_null() {
- // Exercises value_to_nodes Null branch: a mixed sequence containing
- // None alongside nested Vecs hits the `-` children path, which calls
- // value_to_nodes("-", Value::Null).
- #[derive(Serialize, Debug)]
- struct S {
- items: Vec<Option<Vec<i32>>>,
- }
- let val = S {
- items: vec![Some(vec![1, 2]), None, Some(vec![3])],
- };
- let output = serde_kdl::to_string(&val).unwrap();
- assert!(output.contains("#null"));
-}
-
-// ════════════════════════════════════════════════════════════════════════
-// Remaining coverage: ValueDeserializer::deserialize_any Integer/Float
-// ════════════════════════════════════════════════════════════════════════
-
-#[test]
-fn field_deserialize_any_integer_limitation() {
- // ValueDeserializer::deserialize_any calls visit_i128 for integers,
- // but serde's Content buffer (used by untagged enums) doesn't handle
- // i128. This means integers through deserialize_any only work with
- // serde types that accept visit_i128 directly. The Integer branch in
- // ValueDeserializer::deserialize_any is excluded from coverage.
- #[derive(Deserialize, Debug)]
- #[serde(untagged)]
- enum DynVal {
- Num(i64),
- }
- #[derive(Deserialize, Debug)]
- struct S {
- data: DynVal,
+ enum Val {
+ Point(f64, f64, f64),
}
- let result = serde_kdl::from_str::<S>(r#"data 42"#);
- assert!(result.is_err());
-}
-
-#[test]
-fn field_deserialize_any_float() {
- // Single float arg → FieldDeserializer::deserialize_any → ValueDeserializer::deserialize_any (Float branch)
#[derive(Deserialize, Debug, PartialEq)]
- #[serde(untagged)]
- enum DynVal {
- Float(f64),
- }
- #[derive(Deserialize, Debug)]
struct S {
- data: DynVal,
- }
- let input = r#"data 3.14"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- match val.data {
- DynVal::Float(f) => assert!((f - 3.14).abs() < 0.001),
+ data: Val,
}
+ let val: S = serde_kdl::from_str("data {\n Point 1.0 2.0 3.0\n}\n").unwrap();
+ assert_eq!(val.data, Val::Point(1.0, 2.0, 3.0));
}
#[test]
-fn field_deserialize_any_bool() {
- // Single bool arg → FieldDeserializer::deserialize_any → ValueDeserializer::deserialize_any (Bool branch)
+fn enum_complex_struct_variant_from_props() {
#[derive(Deserialize, Debug, PartialEq)]
- #[serde(untagged)]
- enum DynVal {
- Bool(bool),
- }
- #[derive(Deserialize, Debug)]
- struct S {
- data: DynVal,
- }
- let input = r#"data #true"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- match val.data {
- DynVal::Bool(b) => assert!(b),
+ enum Val {
+ Circle { radius: f64 },
}
-}
-
-#[test]
-fn field_deserialize_any_null() {
- // Single null arg → FieldDeserializer::deserialize_any → ValueDeserializer::deserialize_any (Null branch)
#[derive(Deserialize, Debug, PartialEq)]
- #[serde(untagged)]
- enum DynVal {
- Nothing,
- Str(String),
- }
- #[derive(Deserialize, Debug)]
struct S {
- data: Option<DynVal>,
+ shape: Val,
}
- let input = r#"data #null"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, None);
+ let val: S = serde_kdl::from_str("shape {\n Circle radius=5.0\n}\n").unwrap();
+ assert_eq!(val.shape, Val::Circle { radius: 5.0 });
}
// ════════════════════════════════════════════════════════════════════════
-// Remaining: FieldDeserializer::deserialize_ignored_any
+// Repeated-node Vec<T> tests — data-driven
+//
+// NodeContentDeserializer is exercised when repeated nodes are
+// deserialized into Vec<T>. Each entry tests a different element type.
// ════════════════════════════════════════════════════════════════════════
+deser_repeated_vec!(node_content_bool_in_seq, flag: Vec<bool>, "flag #true\nflag #false\n", vec![true, false]);
+deser_repeated_vec!(node_content_i8_in_seq, val: Vec<i8>, "val 1\nval 2\n", vec![1i8, 2]);
+deser_repeated_vec!(node_content_i16_in_seq, val: Vec<i16>, "val 1\nval 2\n", vec![1i16, 2]);
+deser_repeated_vec!(node_content_i32_in_seq, val: Vec<i32>, "val 1\nval 2\n", vec![1i32, 2]);
+deser_repeated_vec!(node_content_i64_in_seq, val: Vec<i64>, "val 1\nval 2\n", vec![1i64, 2]);
+deser_repeated_vec!(node_content_i128_in_seq, val: Vec<i128>, "val 1\nval 2\n", vec![1i128, 2]);
+deser_repeated_vec!(node_content_u8_in_seq, val: Vec<u8>, "val 1\nval 2\n", vec![1u8, 2]);
+deser_repeated_vec!(node_content_u16_in_seq, val: Vec<u16>, "val 1\nval 2\n", vec![1u16, 2]);
+deser_repeated_vec!(node_content_u32_in_seq, val: Vec<u32>, "val 1\nval 2\n", vec![1u32, 2]);
+deser_repeated_vec!(node_content_u64_in_seq, val: Vec<u64>, "val 1\nval 2\n", vec![1u64, 2]);
+deser_repeated_vec!(node_content_u128_in_seq, val: Vec<u128>, "val 1\nval 2\n", vec![1u128, 2]);
+deser_repeated_vec!(node_content_f32_in_seq, val: Vec<f32>, "val 1.5\nval 2.5\n", vec![1.5f32, 2.5]);
+deser_repeated_vec!(node_content_f64_in_seq, val: Vec<f64>, "val 1.5\nval 2.5\n", vec![1.5f64, 2.5]);
+deser_repeated_vec!(node_content_char_in_seq, ch: Vec<char>, "ch \"A\"\nch \"B\"\n", vec!['A', 'B']);
+deser_repeated_vec!(node_content_string_in_seq, name: Vec<String>, "name \"Alice\"\nname \"Bob\"\n", vec![String::from("Alice"), String::from("Bob")]);
+deser_repeated_vec!(node_content_unit_in_seq, marker: Vec<()>, "marker\nmarker\n", vec![(), ()]);
+
+deser_repeated_vec!(node_content_option_in_seq, val: Vec<Option<i32>>, "val 1\nval #null\nval 3\n", vec![Some(1), None, Some(3)]);
+
+deser_repeated_vec!(
+ node_content_tuple_in_seq, coords: Vec<(f64, f64)>,
+ "coords 1.0 2.0\ncoords 3.0 4.0\n",
+ vec![(1.0, 2.0), (3.0, 4.0)]
+);
+
+deser_repeated_vec!(
+ node_content_enum_in_seq, color: Vec<Color>,
+ "color \"Red\"\ncolor \"Blue\"\n",
+ vec![Color::Red, Color::Blue]
+);
+
+deser_repeated_vec!(
+ node_content_multi_arg_as_seq, coords: Vec<Vec<f64>>,
+ "coords 1.0 2.0 3.0\ncoords 4.0 5.0 6.0\n",
+ vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]
+);
+
+deser_repeated_vec!(
+ node_content_bytes_in_seq, data: Vec<Vec<u8>>,
+ "data 1 2 3\ndata 4 5 6\n",
+ vec![vec![1u8, 2, 3], vec![4, 5, 6]]
+);
+
+deser_repeated_vec!(
+ node_content_dash_children_in_seq, group: Vec<Vec<i32>>,
+ "group {\n - 1\n - 2\n}\ngroup {\n - 3\n - 4\n}\n",
+ vec![vec![1, 2], vec![3, 4]]
+);
+
+deser_repeated_vec!(
+ node_content_non_dash_children_seq, group: Vec<Vec<i32>>,
+ "group {\n item 1\n item 2\n}\ngroup {\n item 3\n}\n",
+ vec![vec![1, 2], vec![3]]
+);
+
+deser_repeated_vec!(
+ node_content_seq_empty_children_fallthrough, vals: Vec<Vec<i32>>,
+ "vals 1 2 3\nvals 4 5 6\n",
+ vec![vec![1, 2, 3], vec![4, 5, 6]]
+);
+
+deser_repeated_vec!(
+ node_content_seq_args_fallthrough_empty_children, vals: Vec<Vec<i32>>,
+ "vals {\n}\nvals {\n}\n",
+ vec![Vec::<i32>::new(), Vec::<i32>::new()]
+);
+
+// ── Newtype/tuple struct in repeated nodes ─────────────────────────────
+
#[test]
-fn field_ignored_any_with_children() {
- // A struct that ignores a field with children content
- let input = r#"
-name "test"
-complex {
- nested "value"
- deep {
- x 1
- }
-}
-"#;
- #[derive(Deserialize, Debug)]
+fn node_content_newtype_in_seq() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Meters(f64);
+ #[derive(Deserialize, Debug, PartialEq)]
struct S {
- name: String,
+ dist: Vec<Meters>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, "test");
+ let val: S = serde_kdl::from_str("dist 1.0\ndist 2.0\n").unwrap();
+ assert_eq!(val.dist, vec![Meters(1.0), Meters(2.0)]);
}
-// ════════════════════════════════════════════════════════════════════════
-// Remaining: FieldDeserializer::deserialize_enum error (multi-child)
-// ════════════════════════════════════════════════════════════════════════
-
#[test]
-fn field_deserialize_enum_multi_children_error() {
- // A node with multiple children nodes can't determine which is the variant
- let input = r#"
-shape {
- Circle {
- radius 5.0
- }
- Rectangle {
- width 10.0
+fn node_content_tuple_struct_in_seq() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Pair(f64, f64);
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ pair: Vec<Pair>,
}
+ let val: S = serde_kdl::from_str("pair 1.0 2.0\npair 3.0 4.0\n").unwrap();
+ assert_eq!(val.pair, vec![Pair(1.0, 2.0), Pair(3.0, 4.0)]);
}
-"#;
- #[derive(Deserialize, Debug)]
+
+#[test]
+fn node_content_unit_struct_in_seq() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Marker;
+ #[derive(Deserialize, Debug, PartialEq)]
struct S {
- shape: Shape,
+ tag: Vec<Marker>,
}
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
+ let val: S = serde_kdl::from_str("tag\ntag\n").unwrap();
+ assert_eq!(val.tag, vec![Marker, Marker]);
}
+// ── Struct from properties in repeated nodes ───────────────────────────
+
#[test]
-fn node_content_seq_empty_children_fallthrough() {
- // A repeated node with empty children block falls through to args
- let input = r#"
-vals 1 2 3
-vals 4 5 6
-"#;
+fn node_content_struct_from_properties_in_seq() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Point {
+ x: f64,
+ y: f64,
+ }
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- vals: Vec<Vec<i32>>,
+ point: Vec<Point>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![vec![1, 2, 3], vec![4, 5, 6]]);
+ let val: S = serde_kdl::from_str("point x=1.0 y=2.0\npoint x=3.0 y=4.0\n").unwrap();
+ assert_eq!(
+ val.point,
+ vec![Point { x: 1.0, y: 2.0 }, Point { x: 3.0, y: 4.0 }]
+ );
}
#[test]
-fn node_content_seq_args_fallthrough_empty_children() {
- // NodeContentDeserializer::deserialize_seq: node with empty children block
- // falls through to args. Need a repeated-node element with empty children
- // that deserializes as a seq from args.
- let input = "vals {\n}\nvals {\n}\n";
+fn node_content_single_arg_struct_in_seq() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct W {
+ value: i32,
+ }
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- vals: Vec<Vec<i32>>,
+ item: Vec<W>,
}
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![Vec::<i32>::new(), Vec::<i32>::new()]);
+ let val: S = serde_kdl::from_str("item 10\nitem 20\n").unwrap();
+ assert_eq!(val.item, vec![W { value: 10 }, W { value: 20 }]);
}
#[test]
fn node_content_struct_empty_node_in_seq() {
- // NodeContentDeserializer::deserialize_struct fallthrough:
- // An empty node targeting a multi-field struct inside a Vec.
#[derive(Deserialize, Debug, PartialEq, Default)]
struct Item {
#[serde(default)]
@@ -2847,345 +1622,438 @@ fn node_content_struct_empty_node_in_seq() {
struct S {
item: Vec<Item>,
}
- let input = "item\nitem\n";
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str("item\nitem\n").unwrap();
assert_eq!(
val.item,
vec![Item { a: None, b: None }, Item { a: None, b: None }]
);
}
+// ── Map from properties/children in repeated nodes ─────────────────────
+
#[test]
-fn node_content_unit_struct_in_seq() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Marker;
- #[derive(Deserialize, Debug, PartialEq)]
+fn node_content_map_from_properties_in_seq() {
+ #[derive(Deserialize, Debug)]
struct S {
- tag: Vec<Marker>,
+ entry: Vec<HashMap<String, String>>,
}
- let input = "tag\ntag\n";
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.tag, vec![Marker, Marker]);
+ let val: S = serde_kdl::from_str("entry a=\"1\" b=\"2\"\nentry c=\"3\"\n").unwrap();
+ assert_eq!(val.entry.len(), 2);
+ assert_eq!(val.entry[0].get("a"), Some(&"1".into()));
}
#[test]
-fn node_content_bytes_custom_deser_in_seq() {
- // Custom type that calls deserialize_bytes inside a Vec (repeated nodes).
- // This exercises NodeContentDeserializer::deserialize_bytes.
- #[derive(Debug, PartialEq)]
- struct ByteData(Vec<u8>);
+fn node_content_map_from_children() {
+ #[derive(Deserialize, Debug)]
+ struct S {
+ entry: Vec<HashMap<String, String>>,
+ }
+ let val: S =
+ serde_kdl::from_str("entry {\n a \"1\"\n b \"2\"\n}\nentry {\n c \"3\"\n}\n")
+ .unwrap();
+ assert_eq!(val.entry[0].get("a"), Some(&"1".into()));
+ assert_eq!(val.entry[1].get("c"), Some(&"3".into()));
+}
- impl<'de> Deserialize<'de> for ByteData {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = ByteData;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "byte data")
- }
- fn visit_seq<A: serde::de::SeqAccess<'de>>(
- self,
- mut seq: A,
- ) -> Result<ByteData, A::Error> {
- let mut bytes = Vec::new();
- while let Some(b) = seq.next_element()? {
- bytes.push(b);
- }
- Ok(ByteData(bytes))
- }
- }
- de.deserialize_bytes(V)
- }
+#[test]
+fn node_content_map_from_props_no_children() {
+ #[derive(Deserialize, Debug)]
+ struct S {
+ entry: Vec<HashMap<String, String>>,
}
+ let val: S = serde_kdl::from_str("entry a=\"1\" b=\"2\"\nentry c=\"3\"\n").unwrap();
+ assert_eq!(val.entry[0].get("a"), Some(&"1".into()));
+ assert_eq!(val.entry[1].get("c"), Some(&"3".into()));
+}
+// ── Complex enum in repeated nodes ─────────────────────────────────────
+
+#[test]
+fn node_content_complex_enum_in_seq() {
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- data: Vec<ByteData>,
+ shape: Vec<Shape>,
}
- let input = "data 1 2 3\ndata 4 5\n";
- let val: S = serde_kdl::from_str(input).unwrap();
+ let val: S = serde_kdl::from_str(
+ "shape {\n Circle {\n radius 5.0\n }\n}\nshape {\n Rectangle {\n width 10.0\n height 20.0\n }\n}\n"
+ ).unwrap();
+ assert_eq!(val.shape[0], Shape::Circle { radius: 5.0 });
assert_eq!(
- val.data,
- vec![ByteData(vec![1, 2, 3]), ByteData(vec![4, 5])]
+ val.shape[1],
+ Shape::Rectangle {
+ width: 10.0,
+ height: 20.0
+ }
);
}
-#[test]
-fn node_content_byte_buf_custom_deser_in_seq() {
- // Custom type that calls deserialize_byte_buf inside a Vec (repeated nodes).
- #[derive(Debug, PartialEq)]
- struct ByteBufData(Vec<u8>);
+// ── Extra fields in repeated nodes (ignored_any) ───────────────────────
- impl<'de> Deserialize<'de> for ByteBufData {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = ByteBufData;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "byte buf data")
- }
- fn visit_seq<A: serde::de::SeqAccess<'de>>(
- self,
- mut seq: A,
- ) -> Result<ByteBufData, A::Error> {
- let mut bytes = Vec::new();
- while let Some(b) = seq.next_element()? {
- bytes.push(b);
- }
- Ok(ByteBufData(bytes))
- }
- }
- de.deserialize_byte_buf(V)
- }
+#[test]
+fn node_content_ignored_any() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Item {
+ name: String,
}
-
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- data: Vec<ByteBufData>,
+ item: Vec<Item>,
}
- let input = "data 1 2 3\ndata 4 5\n";
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(
- val.data,
- vec![ByteBufData(vec![1, 2, 3]), ByteBufData(vec![4, 5])]
- );
+ let val: S = serde_kdl::from_str("item {\n name \"test\"\n extra \"ignored\"\n}\nitem {\n name \"test2\"\n bonus 99\n}\n").unwrap();
+ assert_eq!(val.item.len(), 2);
+ assert_eq!(val.item[0].name, "test");
}
-#[test]
-fn field_deserializer_byte_buf() {
- // Custom type that calls deserialize_byte_buf on a FieldDeserializer.
- #[derive(Debug, PartialEq)]
- struct OwnedBytes(Vec<u8>);
-
- impl<'de> Deserialize<'de> for OwnedBytes {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = OwnedBytes;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "owned bytes")
- }
- fn visit_seq<A: serde::de::SeqAccess<'de>>(
- self,
- mut seq: A,
- ) -> Result<OwnedBytes, A::Error> {
- let mut bytes = Vec::new();
- while let Some(b) = seq.next_element()? {
- bytes.push(b);
- }
- Ok(OwnedBytes(bytes))
- }
- }
- de.deserialize_byte_buf(V)
- }
- }
+// ── Repeated-node Vec for same-type via MultiNodeSeqAccess ─────────────
+#[test]
+fn node_content_with_children() {
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- data: OwnedBytes,
+ server: Vec<Server>,
}
- let input = "data 1 2 3";
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, OwnedBytes(vec![1, 2, 3]));
+ let val: S = serde_kdl::from_str(
+ "server {\n host \"a\"\n port 1\n}\nserver {\n host \"b\"\n port 2\n}\n",
+ )
+ .unwrap();
+ assert_eq!(val.server.len(), 2);
}
-#[test]
-fn field_deserializer_str() {
- // Custom type that calls deserialize_str on a FieldDeserializer.
- #[derive(Debug, PartialEq)]
- struct BorrowStr(String);
+deser_repeated_vec!(
+ node_content_identifier_in_enum_seq, color: Vec<Color>,
+ "color \"Red\"\ncolor \"Green\"\n",
+ vec![Color::Red, Color::Green]
+);
- impl<'de> Deserialize<'de> for BorrowStr {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = BorrowStr;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "a string")
- }
- fn visit_str<E>(self, v: &str) -> Result<BorrowStr, E> {
- Ok(BorrowStr(v.to_string()))
- }
- }
- de.deserialize_str(V)
- }
- }
+deser_repeated_vec!(
+ node_content_string_in_repeated_nodes, val: Vec<String>,
+ "val \"hello\"\nval \"world\"\n",
+ vec![String::from("hello"), String::from("world")]
+);
+
+// ════════════════════════════════════════════════════════════════════════
+// Multi-arg Vec<T> tests — data-driven
+//
+// ArgsSeqAccess is exercised when a single node has multiple positional
+// arguments deserialized into Vec<T>.
+// ════════════════════════════════════════════════════════════════════════
+
+deser_field!(args_seq_bool_values, flags: Vec<bool>, "flags #true #false #true", vec![true, false, true]);
+deser_field!(args_seq_string_values, names: Vec<String>, r#"names "Alice" "Bob""#, vec![String::from("Alice"), String::from("Bob")]);
+deser_field!(args_seq_char_values, letters: Vec<char>, r#"letters "A" "B" "C""#, vec!['A', 'B', 'C']);
+deser_field!(args_seq_i128_values, vals: Vec<i128>, "vals 100 200 300", vec![100i128, 200, 300]);
+deser_field!(args_seq_u128_values, vals: Vec<u128>, "vals 100 200 300", vec![100u128, 200, 300]);
+deser_field!(args_seq_f32_values, vals: Vec<f32>, "vals 1.5 2.5 3.5", vec![1.5f32, 2.5, 3.5]);
+deser_field!(args_seq_f64_values, vals: Vec<f64>, "vals 1.5 2.5 3.5", vec![1.5f64, 2.5, 3.5]);
+deser_field!(args_seq_enum_values, colors: Vec<Color>, r#"colors "Red" "Blue" "Green""#, vec![Color::Red, Color::Blue, Color::Green]);
+deser_field!(args_seq_option_with_null, vals: Vec<Option<String>>, r#"vals "hello" #null "world""#, vec![Some(String::from("hello")), None, Some(String::from("world"))]);
+deser_field!(value_deserializer_bytes_from_string, data: Vec<u8>, "data 104 101 108", vec![104u8, 101, 108]);
+
+deser_field!(value_deserializer_unit_null_in_args, vals: Vec<()>, "vals #null #null", vec![(), ()]);
+#[test]
+fn args_seq_newtype_values() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Meters(f64);
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- name: BorrowStr,
+ dists: Vec<Meters>,
}
- let input = r#"name "hello""#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.name, BorrowStr("hello".into()));
+ let val: S = serde_kdl::from_str("dists 1.0 2.0 3.0").unwrap();
+ assert_eq!(val.dists, vec![Meters(1.0), Meters(2.0), Meters(3.0)]);
}
#[test]
-fn node_content_str_custom_deser_in_seq() {
- // Custom type calling deserialize_str inside a Vec (repeated nodes).
- #[derive(Debug, PartialEq)]
- struct BorrowStr2(String);
-
- impl<'de> Deserialize<'de> for BorrowStr2 {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = BorrowStr2;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "a string")
- }
- fn visit_str<E>(self, v: &str) -> Result<BorrowStr2, E> {
- Ok(BorrowStr2(v.to_string()))
- }
- }
- de.deserialize_str(V)
- }
- }
-
+fn value_deserializer_unit_struct_in_args() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Marker;
#[derive(Deserialize, Debug, PartialEq)]
struct S {
- name: Vec<BorrowStr2>,
+ vals: Vec<Marker>,
}
- let input = "name \"hello\"\nname \"world\"\n";
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(
- val.name,
- vec![BorrowStr2("hello".into()), BorrowStr2("world".into())]
- );
+ let val: S = serde_kdl::from_str("vals #null #null").unwrap();
+ assert_eq!(val.vals, vec![Marker, Marker]);
}
-#[test]
-fn value_deserializer_bytes_from_string_seq() {
- // Custom type that calls deserialize_bytes on a ValueDeserializer
- // (reached via ArgsSeqAccess from a multi-arg node).
- #[derive(Debug, PartialEq)]
- struct RawBytes(Vec<u8>);
+// ════════════════════════════════════════════════════════════════════════
+// Custom Deserialize impl tests — data-driven via macro
+//
+// These test code paths reachable only through custom Deserialize impls
+// that call specific deserializer methods (deserialize_str,
+// deserialize_bytes, deserialize_byte_buf).
+// ════════════════════════════════════════════════════════════════════════
- impl<'de> Deserialize<'de> for RawBytes {
- fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
- struct V;
- impl<'de> serde::de::Visitor<'de> for V {
- type Value = RawBytes;
- fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "bytes")
+/// Generates a test with a custom Deserialize impl that calls the given
+/// deserializer method and forwards to visit_seq (collecting Vec<u8>).
+macro_rules! test_custom_seq_deser {
+ ($name:ident, $deser_method:ident, $field:ident, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, PartialEq)]
+ struct Custom(Vec<u8>);
+ impl<'de> Deserialize<'de> for Custom {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = Custom;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "custom")
+ }
+ fn visit_seq<A: serde::de::SeqAccess<'de>>(
+ self,
+ mut seq: A,
+ ) -> Result<Custom, A::Error> {
+ let mut v = Vec::new();
+ while let Some(b) = seq.next_element()? {
+ v.push(b);
+ }
+ Ok(Custom(v))
+ }
+ }
+ de.$deser_method(V)
}
- fn visit_bytes<E>(self, v: &[u8]) -> Result<RawBytes, E> {
- Ok(RawBytes(v.to_vec()))
+ }
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ $field: $crate::deser_target_type!($expected),
+ }
+ let val: S = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val.$field, $expected);
+ }
+ };
+}
+
+// Helper: we need the type of the field to vary (Vec<Custom> vs Custom)
+// Let's just inline the struct definition in each test instead.
+
+/// Generates a test with a custom Deserialize impl calling `$deser_method`
+/// and using `visit_str` to produce a String wrapper.
+macro_rules! test_custom_str_deser {
+ ($name:ident, $deser_method:ident, field_type: $field_ty:ty, $field:ident, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, PartialEq)]
+ struct Custom(String);
+ impl<'de> Deserialize<'de> for Custom {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = Custom;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "custom str")
+ }
+ fn visit_str<E>(self, v: &str) -> Result<Custom, E> {
+ Ok(Custom(v.to_string()))
+ }
+ }
+ de.$deser_method(V)
}
}
- de.deserialize_bytes(V)
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ $field: $field_ty,
+ }
+ let val: S = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val.$field, $expected);
}
- }
+ };
+}
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- data: Vec<RawBytes>,
- }
- let input = r#"data "hello" "world""#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(
- val.data,
- vec![RawBytes(b"hello".to_vec()), RawBytes(b"world".to_vec())]
- );
+/// Generates a test with a custom Deserialize impl calling `$deser_method`
+/// and using `visit_bytes` to produce a Vec<u8> wrapper.
+macro_rules! test_custom_bytes_deser {
+ ($name:ident, $deser_method:ident, field_type: $field_ty:ty, $field:ident, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, PartialEq)]
+ struct Custom(Vec<u8>);
+ impl<'de> Deserialize<'de> for Custom {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = Custom;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "custom bytes")
+ }
+ fn visit_bytes<E>(self, v: &[u8]) -> Result<Custom, E> {
+ Ok(Custom(v.to_vec()))
+ }
+ }
+ de.$deser_method(V)
+ }
+ }
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ $field: $field_ty,
+ }
+ let val: S = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val.$field, $expected);
+ }
+ };
+}
+
+/// Generates a test with a custom Deserialize impl calling `$deser_method`
+/// and using `visit_seq` to produce a Vec<u8> wrapper.
+macro_rules! test_custom_seq_bytes_deser {
+ ($name:ident, $deser_method:ident, field_type: $field_ty:ty, $field:ident, $input:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ #[derive(Debug, PartialEq)]
+ struct Custom(Vec<u8>);
+ impl<'de> Deserialize<'de> for Custom {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = Custom;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "custom seq bytes")
+ }
+ fn visit_seq<A: serde::de::SeqAccess<'de>>(
+ self,
+ mut seq: A,
+ ) -> Result<Custom, A::Error> {
+ let mut v = Vec::new();
+ while let Some(b) = seq.next_element()? {
+ v.push(b);
+ }
+ Ok(Custom(v))
+ }
+ }
+ de.$deser_method(V)
+ }
+ }
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ $field: $field_ty,
+ }
+ let val: S = serde_kdl::from_str($input).unwrap();
+ assert_eq!(val.$field, $expected);
+ }
+ };
}
+// -- FieldDeserializer paths --
+
+test_custom_str_deser!(
+ field_deserializer_str, deserialize_str,
+ field_type: Custom, name,
+ r#"name "hello""#,
+ Custom(String::from("hello"))
+);
+
+test_custom_seq_bytes_deser!(
+ field_deserializer_byte_buf, deserialize_byte_buf,
+ field_type: Custom, data,
+ "data 1 2 3",
+ Custom(vec![1, 2, 3])
+);
+
+// -- NodeContentDeserializer paths (via Vec<Custom> in repeated nodes) --
+
+test_custom_seq_bytes_deser!(
+ node_content_bytes_custom_deser_in_seq, deserialize_bytes,
+ field_type: Vec<Custom>, data,
+ "data 1 2 3\ndata 4 5\n",
+ vec![Custom(vec![1, 2, 3]), Custom(vec![4, 5])]
+);
+
+test_custom_seq_bytes_deser!(
+ node_content_byte_buf_custom_deser_in_seq, deserialize_byte_buf,
+ field_type: Vec<Custom>, data,
+ "data 1 2 3\ndata 4 5\n",
+ vec![Custom(vec![1, 2, 3]), Custom(vec![4, 5])]
+);
+
+test_custom_str_deser!(
+ node_content_str_custom_deser_in_seq, deserialize_str,
+ field_type: Vec<Custom>, name,
+ "name \"hello\"\nname \"world\"\n",
+ vec![Custom(String::from("hello")), Custom(String::from("world"))]
+);
+
+// -- ValueDeserializer paths (via Vec<Custom> in multi-arg nodes) --
+
+test_custom_bytes_deser!(
+ value_deserializer_bytes_from_string_seq, deserialize_bytes,
+ field_type: Vec<Custom>, data,
+ r#"data "hello" "world""#,
+ vec![Custom(b"hello".to_vec()), Custom(b"world".to_vec())]
+);
+
+test_custom_bytes_deser!(
+ value_deserializer_byte_buf_via_seq, deserialize_byte_buf,
+ field_type: Vec<Custom>, data,
+ r#"data "hello""#,
+ vec![Custom(b"hello".to_vec())]
+);
+
+// -- ValueDeserializer error: deserialize_bytes on non-string --
+
#[test]
fn value_deserializer_bytes_type_mismatch_via_seq() {
- // Exercise the error path in ValueDeserializer::deserialize_bytes
- // when a non-string value is encountered.
#[derive(Debug)]
- struct RawBytes2(Vec<u8>);
-
- impl<'de> Deserialize<'de> for RawBytes2 {
+ struct Custom(Vec<u8>);
+ impl<'de> Deserialize<'de> for Custom {
fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
- type Value = RawBytes2;
+ type Value = Custom;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "bytes")
}
- fn visit_bytes<E>(self, v: &[u8]) -> Result<RawBytes2, E> {
- Ok(RawBytes2(v.to_vec()))
+ fn visit_bytes<E>(self, v: &[u8]) -> Result<Custom, E> {
+ Ok(Custom(v.to_vec()))
}
}
de.deserialize_bytes(V)
}
}
-
#[derive(Deserialize, Debug)]
struct S {
- data: Vec<RawBytes2>,
+ #[allow(dead_code)]
+ data: Vec<Custom>,
}
- let input = r#"data 42"#;
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
+ assert!(serde_kdl::from_str::<S>("data 42").is_err());
}
-#[test]
-fn value_deserializer_byte_buf_via_seq() {
- // Exercise ValueDeserializer::deserialize_byte_buf
- #[derive(Debug, PartialEq)]
- struct OwnedRawBytes(Vec<u8>);
+// -- FieldDeserializer error: deserialize_bytes on non-seq --
- impl<'de> Deserialize<'de> for OwnedRawBytes {
+#[test]
+fn value_deserializer_bytes_type_mismatch() {
+ #[derive(Debug)]
+ struct ByteString(Vec<u8>);
+ impl<'de> Deserialize<'de> for ByteString {
fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
- type Value = OwnedRawBytes;
+ type Value = ByteString;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "bytes")
}
- fn visit_bytes<E>(self, v: &[u8]) -> Result<OwnedRawBytes, E> {
- Ok(OwnedRawBytes(v.to_vec()))
+ fn visit_bytes<E>(self, v: &[u8]) -> Result<ByteString, E> {
+ Ok(ByteString(v.to_vec()))
}
}
- de.deserialize_byte_buf(V)
+ de.deserialize_bytes(V)
}
}
-
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- data: Vec<OwnedRawBytes>,
- }
- let input = r#"data "hello""#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.data, vec![OwnedRawBytes(b"hello".to_vec())]);
-}
-
-#[test]
-fn value_deserializer_unit_null_in_args() {
- // A multi-arg node with null values deserialized as Vec<()>.
- // Each null arg goes through ValueDeserializer::deserialize_unit.
- let input = r#"vals #null #null"#;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- vals: Vec<()>,
- }
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![(), ()]);
-}
-
-#[test]
-fn value_deserializer_unit_mismatch_in_args() {
- // Non-null value deserialized as unit → error
- let input = r#"vals 42"#;
#[derive(Deserialize, Debug)]
struct S {
- vals: Vec<()>,
+ #[allow(dead_code)]
+ data: ByteString,
}
- let result = serde_kdl::from_str::<S>(input);
- assert!(result.is_err());
+ assert!(serde_kdl::from_str::<S>("data 42").is_err());
}
+// ════════════════════════════════════════════════════════════════════════
+// Miscellaneous edge case tests
+// ════════════════════════════════════════════════════════════════════════
+
#[test]
-fn value_deserializer_unit_struct_in_args() {
- #[derive(Deserialize, Debug, PartialEq)]
- struct Marker;
- #[derive(Deserialize, Debug, PartialEq)]
- struct S {
- vals: Vec<Marker>,
- }
- let input = r#"vals #null #null"#;
- let val: S = serde_kdl::from_str(input).unwrap();
- assert_eq!(val.vals, vec![Marker, Marker]);
+fn value_deserializer_seq_error() {
+ let err = serde_kdl::Error::TypeMismatch {
+ expected: "sequence",
+ got: "scalar value".into(),
+ };
+ assert!(err.to_string().contains("sequence"));
}