use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use url::Url;
use serde::{Deserialize, Serialize};
use serde_structprop::{from_str, to_string};
#[derive(Debug, Deserialize, PartialEq)]
struct Simple {
hostname: String,
port: u16,
}
#[test]
fn de_simple_struct() {
let input = "hostname = localhost\nport = 8080\n";
let cfg: Simple = from_str(input).unwrap();
assert_eq!(cfg.hostname, "localhost");
assert_eq!(cfg.port, 8080);
}
#[test]
fn de_quoted_value() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
message: String,
}
let input = "message = \"hello world\"";
let s: S = from_str(input).unwrap();
assert_eq!(s.message, "hello world");
}
#[test]
fn de_nested_struct() {
#[derive(Debug, Deserialize, PartialEq)]
struct DbConn {
hostname: String,
port: u16,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Config {
database: DbConn,
}
let input = "database {\n hostname = localhost\n port = 5432\n}\n";
let cfg: Config = from_str(input).unwrap();
assert_eq!(cfg.database.hostname, "localhost");
assert_eq!(cfg.database.port, 5432);
}
#[test]
fn de_vec_of_strings() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
tables: Vec<String>,
}
let input = "tables = { Table1 Table2 Table3 }\n";
let s: S = from_str(input).unwrap();
assert_eq!(s.tables, vec!["Table1", "Table2", "Table3"]);
}
#[test]
fn de_vec_of_integers() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
ports: Vec<u16>,
}
let input = "ports = { 80 443 8080 }\n";
let s: S = from_str(input).unwrap();
assert_eq!(s.ports, vec![80u16, 443, 8080]);
}
#[test]
fn de_bool_field() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
enabled: bool,
}
let input = "enabled = true\n";
let s: S = from_str(input).unwrap();
assert!(s.enabled);
}
#[test]
fn de_option_some() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
value: Option<u32>,
}
let input = "value = 42\n";
let s: S = from_str(input).unwrap();
assert_eq!(s.value, Some(42));
}
#[test]
fn de_comments_ignored() {
let input = "# This is a comment\nhostname = server\nport = 9000\n";
let cfg: Simple = from_str(input).unwrap();
assert_eq!(cfg.hostname, "server");
assert_eq!(cfg.port, 9000);
}
#[test]
fn de_full_example() {
#[derive(Debug, Deserialize, PartialEq)]
struct DbConn {
hostname: String,
username: String,
password: String,
port: u16,
name: String,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Config {
database: DbConn,
tables: Vec<String>,
}
let input = "
# This is a simple example config file
database {
hostname = localhost
username = dbuser
password = secret
port = 12361
name = TheDatabase
}
tables = { Table1 Table2 }
";
let cfg: Config = from_str(input).unwrap();
assert_eq!(cfg.database.hostname, "localhost");
assert_eq!(cfg.database.username, "dbuser");
assert_eq!(cfg.database.password, "secret");
assert_eq!(cfg.database.port, 12361);
assert_eq!(cfg.database.name, "TheDatabase");
assert_eq!(cfg.tables, vec!["Table1", "Table2"]);
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Config {
hostname: String,
port: u16,
}
#[test]
fn ser_simple_struct() {
let cfg = Config {
hostname: "localhost".into(),
port: 8080,
};
let out = to_string(&cfg).unwrap();
assert!(out.contains("hostname = localhost"), "got: {out}");
assert!(out.contains("port = 8080"), "got: {out}");
}
#[test]
fn ser_quoted_strings() {
#[derive(Serialize)]
struct S {
message: String,
}
let s = S {
message: "hello world".into(),
};
let out = to_string(&s).unwrap();
assert!(out.contains("message = \"hello world\""), "got: {out}");
}
#[test]
fn ser_nested_struct() {
#[derive(Serialize)]
struct Inner {
x: u32,
}
#[derive(Serialize)]
struct Outer {
inner: Inner,
}
let o = Outer {
inner: Inner { x: 7 },
};
let out = to_string(&o).unwrap();
assert!(out.contains("inner {"), "got: {out}");
assert!(out.contains("x = 7"), "got: {out}");
}
#[test]
fn ser_vec_of_strings() {
#[derive(Serialize)]
struct S {
items: Vec<String>,
}
let s = S {
items: vec!["a".into(), "b".into(), "c".into()],
};
let out = to_string(&s).unwrap();
assert!(out.contains("items"), "got: {out}");
assert!(out.contains('a'), "got: {out}");
assert!(out.contains('b'), "got: {out}");
}
#[test]
fn roundtrip_simple() {
let original = Config {
hostname: "myserver".into(),
port: 3000,
};
let serialized = to_string(&original).unwrap();
let deserialized: Config = from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn roundtrip_nested() {
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Inner {
value: String,
count: u32,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Outer {
name: String,
inner: Inner,
}
let original = Outer {
name: "test".into(),
inner: Inner {
value: "foo".into(),
count: 42,
},
};
let serialized = to_string(&original).unwrap();
let deserialized: Outer = from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn roundtrip_vec() {
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct S {
tags: Vec<String>,
}
let original = S {
tags: vec!["rust".into(), "serde".into(), "config".into()],
};
let serialized = to_string(&original).unwrap();
let deserialized: S = from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn de_parser_error_on_missing_term() {
let result = from_str::<Simple>("hostname\n");
assert!(
result.is_err(),
"expected parse error for bare key with no value"
);
}
#[test]
fn de_inline_comment() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
key: String,
}
let input = "key = value # this is an inline comment\n";
let s: S = from_str(input).unwrap();
assert_eq!(s.key, "value");
}
#[test]
fn de_quoted_key() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
my_key: String,
}
let input = "\"my_key\" = hello\n";
let s: S = from_str(input).unwrap();
assert_eq!(s.my_key, "hello");
}
#[test]
fn de_empty_object() {
#[derive(Debug, Deserialize, PartialEq)]
struct Inner {}
#[derive(Debug, Deserialize, PartialEq)]
struct Outer {
section: Inner,
}
let input = "section {\n}\n";
let outer: Outer = from_str(input).unwrap();
let _ = outer; }
#[test]
fn de_object_key_value_single_line() {
#[derive(Debug, Deserialize, PartialEq)]
struct Inner {
x: u32,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Outer {
section: Inner,
}
let input = "section {\n x = 42\n}\n";
let outer: Outer = from_str(input).unwrap();
assert_eq!(outer.section.x, 42);
}
#[test]
fn de_nested_objects_mixed_array() {
#[derive(Debug, Deserialize, PartialEq)]
struct Inner {
tags: Vec<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Outer {
section: Inner,
}
let input = "section {\n tags = { alpha beta }\n}\n";
let outer: Outer = from_str(input).unwrap();
assert_eq!(outer.section.tags, vec!["alpha", "beta"]);
}
#[test]
fn de_false_bool() {
#[derive(Debug, Deserialize, PartialEq)]
struct S {
flag: bool,
}
let input = "flag = false\n";
let s: S = from_str(input).unwrap();
assert!(!s.flag);
}
#[test]
fn ser_dump_list_exact() {
#[derive(Serialize)]
struct S {
a: Vec<String>,
}
let s = S {
a: vec!["a".into(), "b".into(), "c".into()],
};
let out = to_string(&s).unwrap();
assert_eq!(out, "a = {\n a\n b\n c\n}\n", "got: {out:?}");
}
#[test]
fn ser_dump_dict_exact() {
#[derive(Serialize)]
struct Inner {
b: String,
}
#[derive(Serialize)]
struct Outer {
a: Inner,
}
let o = Outer {
a: Inner { b: "c".into() },
};
let out = to_string(&o).unwrap();
assert_eq!(out, "a {\n b = c\n}\n", "got: {out:?}");
}
#[test]
fn ser_dump_true_bool() {
#[derive(Serialize)]
struct S {
flag: bool,
}
let out = to_string(&S { flag: true }).unwrap();
assert_eq!(out, "flag = true\n");
}
#[test]
fn ser_dump_false_bool() {
#[derive(Serialize)]
struct S {
flag: bool,
}
let out = to_string(&S { flag: false }).unwrap();
assert_eq!(out, "flag = false\n");
}
#[test]
fn ser_escape_space_in_value() {
#[derive(Serialize)]
struct S {
msg: String,
}
let out = to_string(&S {
msg: "hello world".into(),
})
.unwrap();
assert!(out.contains("msg = \"hello world\""), "got: {out:?}");
}
#[test]
fn ser_object_order_is_kept() {
#[derive(Serialize)]
struct S {
first: u32,
second: u32,
third: u32,
}
let out = to_string(&S {
first: 1,
second: 2,
third: 3,
})
.unwrap();
let pos_first = out.find("first").unwrap();
let pos_second = out.find("second").unwrap();
let pos_third = out.find("third").unwrap();
assert!(
pos_first < pos_second && pos_second < pos_third,
"got: {out:?}"
);
}
#[test]
fn ser_struct_variant_does_not_corrupt_preceding_fields() {
#[derive(Serialize)]
enum Shape {
Circle { radius: u32 },
}
#[derive(Serialize)]
struct Config {
name: String,
shape: Shape,
}
let cfg = Config {
name: "test".into(),
shape: Shape::Circle { radius: 5 },
};
let out = to_string(&cfg).unwrap();
assert_eq!(
out, "name = test\nshape {\n Circle {\n radius = 5\n }\n}\n",
"struct-variant header must appear at the correct position, got:\n{out}"
);
}
#[test]
fn ser_tuple_variant_zero_elements_does_not_panic() {
#[derive(Serialize)]
enum E {
Empty(),
}
let result = to_string(&E::Empty());
assert!(result.is_ok(), "expected Ok, got {result:?}");
}
#[test]
fn ser_tuple_variant_sentinel_collision() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum E {
#[serde(rename = "__variant__Tricky")]
Tricky(u32),
}
let out = to_string(&E::Tricky(42)).unwrap();
assert!(
out.contains("__variant__Tricky"),
"variant name mangled, got:\n{out}"
);
}
#[test]
fn ser_string_with_newline_is_quoted() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct S {
msg: String,
}
let s = S {
msg: "hello\nworld".into(),
};
let out = to_string(&s).unwrap();
assert!(
out.contains('"'),
"expected quoted output for newline-containing string, got:\n{out}"
);
let back: S = from_str(&out).unwrap();
assert_eq!(back, s);
}
#[test]
fn roundtrip_nested_struct_with_newline_string() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Inner {
msg: String,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Outer {
name: String,
inner: Inner,
}
let orig = Outer {
name: "test".into(),
inner: Inner {
msg: "hello\nworld".into(),
},
};
let out = to_string(&orig).unwrap();
let back: Outer = from_str(&out).unwrap();
assert_eq!(back, orig, "round-trip failed; output was:\n{out}");
}
#[test]
fn ser_char_special_chars_are_quoted() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct S {
c: char,
}
for special in [' ', '#', '{', '}', '='] {
let s = S { c: special };
let out = to_string(&s).unwrap();
let back: S = from_str(&out).unwrap();
assert_eq!(
back, s,
"char '{special}' did not round-trip; output was:\n{out}"
);
}
}
#[test]
fn de_duplicate_key_is_an_error() {
use std::collections::HashMap;
let input = "port = 1234\nport = 5678\n";
let result: Result<HashMap<String, String>, _> = from_str(input);
assert!(
result.is_err(),
"expected error for duplicate key, got {result:?}"
);
}
#[test]
fn de_unterminated_quoted_string_is_an_error() {
use std::collections::HashMap;
let input = "key = \"unterminated";
let result: Result<HashMap<String, String>, _> = from_str(input);
assert!(
result.is_err(),
"expected error for unterminated string, got {result:?}"
);
}
#[test]
fn de_large_integer_is_not_silently_coerced_to_float() {
#[derive(Debug, Deserialize)]
struct S {
val: u64,
}
let input = format!("val = {}\n", u64::MAX);
let s: S = from_str(&input).unwrap();
assert_eq!(s.val, u64::MAX);
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum Color {
Red,
Green,
Blue,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct WithEnum {
color: Color,
}
#[test]
fn roundtrip_unit_enum_variant() {
let s = WithEnum { color: Color::Red };
let out = to_string(&s).unwrap();
let back: WithEnum = from_str(&out).unwrap();
assert_eq!(back, s);
}
#[test]
fn roundtrip_newtype_enum_variant() {
let msg = Message::Write("hello".into());
let out = to_string(&msg).unwrap();
let back: Message = from_str(&out).unwrap();
assert_eq!(back, msg);
}
#[test]
fn roundtrip_struct_enum_variant() {
let msg = Message::Move { x: 10, y: 20 };
let out = to_string(&msg).unwrap();
let back: Message = from_str(&out).unwrap();
assert_eq!(back, msg);
}
#[test]
fn roundtrip_tuple_enum_variant() {
let msg = Message::ChangeColor(255, 128, 0);
let out = to_string(&msg).unwrap();
let back: Message = from_str(&out).unwrap();
assert_eq!(back, msg);
}
#[test]
fn roundtrip_option_none() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct S {
#[serde(default)]
value: Option<u32>,
}
let s = S { value: None };
let out = to_string(&s).unwrap();
assert!(
out.contains("null"),
"expected 'null' in output, got:\n{out}"
);
let back: S = from_str(&out).unwrap();
assert_eq!(back, s);
}
#[test]
fn de_option_field_absent_defaults_to_none() {
#[derive(Deserialize, PartialEq, Debug)]
struct S {
name: String,
#[serde(default)]
count: Option<u32>,
}
let s: S = from_str("name = foo\n").unwrap();
assert_eq!(s.count, None);
}
#[test]
fn de_unit_field_roundtrip() {
#[derive(Debug, Deserialize, PartialEq)]
struct Doc {
key: (),
}
let doc: Doc = from_str("key = null\n").expect("expected Ok deserializing null as unit");
assert_eq!(doc.key, ());
let err: Result<Doc, _> = from_str("key = hello\n");
assert!(
err.is_err(),
"expected error deserializing non-null as unit"
);
}
#[test]
fn ser_non_string_map_key_is_an_error() {
use std::collections::HashMap;
let mut int_map: HashMap<u32, &str> = HashMap::new();
int_map.insert(42, "hello");
assert!(
to_string(&int_map).is_err(),
"expected error serializing integer map key"
);
let mut char_map: HashMap<char, &str> = HashMap::new();
char_map.insert('x', "hello");
assert!(
to_string(&char_map).is_err(),
"expected error serializing char map key"
);
}
#[test]
fn ser_empty_string_is_quoted() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
val: String,
}
let original = Cfg { val: String::new() };
let s = to_string(&original).unwrap();
assert!(
s.contains("val = \"\""),
"expected quoted empty string, got: {s:?}"
);
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn de_ipv4_addr() {
#[derive(Deserialize)]
struct Cfg {
addr: Ipv4Addr,
}
let cfg: Cfg = from_str("addr = 10.0.0.1\n").unwrap();
assert_eq!(cfg.addr, Ipv4Addr::new(10, 0, 0, 1));
}
#[test]
fn de_ipv6_addr() {
#[derive(Deserialize)]
struct Cfg {
addr: Ipv6Addr,
}
let cfg: Cfg = from_str("addr = \"::1\"\n").unwrap();
assert_eq!(cfg.addr, Ipv6Addr::LOCALHOST);
}
#[test]
fn de_ip_addr_v4() {
#[derive(Deserialize)]
struct Cfg {
addr: IpAddr,
}
let cfg: Cfg = from_str("addr = 192.168.1.1\n").unwrap();
assert_eq!(cfg.addr, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
}
#[test]
fn de_ip_addr_v6() {
#[derive(Deserialize)]
struct Cfg {
addr: IpAddr,
}
let cfg: Cfg = from_str("addr = \"::1\"\n").unwrap();
assert_eq!(cfg.addr, IpAddr::V6(Ipv6Addr::LOCALHOST));
}
#[test]
fn de_vec_of_ip_addrs() {
#[derive(Deserialize)]
struct Cfg {
addrs: Vec<IpAddr>,
}
let cfg: Cfg = from_str("addrs = { 10.0.0.1 10.0.0.2 }\n").unwrap();
assert_eq!(
cfg.addrs,
vec![
IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
]
);
}
#[test]
fn de_url_field() {
#[derive(Deserialize)]
struct Cfg {
endpoint: Url,
}
let cfg: Cfg = from_str("endpoint = \"http://example.com/foo\"\n").unwrap();
assert_eq!(cfg.endpoint.as_str(), "http://example.com/foo");
}
#[test]
fn de_url_bare_no_path() {
#[derive(Deserialize)]
struct Cfg {
endpoint: Url,
}
let cfg: Cfg = from_str("endpoint = http://example.com\n").unwrap();
assert_eq!(cfg.endpoint.host_str(), Some("example.com"));
}
#[test]
fn de_url_invalid_is_error() {
#[derive(Deserialize)]
struct Cfg {
_endpoint: Url,
}
assert!(from_str::<Cfg>("endpoint = not-a-url\n").is_err());
}
#[test]
fn de_vec_of_urls() {
#[derive(Deserialize)]
struct Cfg {
endpoints: Vec<Url>,
}
let cfg: Cfg =
from_str("endpoints = { \"http://foo.example.com\" \"http://bar.example.com\" }\n")
.unwrap();
assert_eq!(cfg.endpoints[0].host_str(), Some("foo.example.com"));
assert_eq!(cfg.endpoints[1].host_str(), Some("bar.example.com"));
}
#[test]
fn de_char_field() {
#[derive(Deserialize)]
struct Cfg {
sep: char,
}
let cfg: Cfg = from_str("sep = ,\n").unwrap();
assert_eq!(cfg.sep, ',');
}
#[test]
fn roundtrip_char() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
sep: char,
}
let original = Cfg { sep: ',' };
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn de_char_multichar_is_error() {
#[derive(Deserialize)]
struct Cfg {
_sep: char,
}
assert!(from_str::<Cfg>("sep = ab\n").is_err());
}
#[test]
fn roundtrip_bool() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
enabled: bool,
}
for val in [true, false] {
let original = Cfg { enabled: val };
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
}
#[test]
fn de_bool_invalid_is_error() {
#[derive(Deserialize)]
struct Cfg {
_enabled: bool,
}
assert!(from_str::<Cfg>("enabled = yes\n").is_err());
}
#[test]
fn de_u8_out_of_range_is_error() {
#[derive(Deserialize)]
struct Cfg {
_val: u8,
}
assert!(from_str::<Cfg>("val = 300\n").is_err());
}
#[test]
fn de_u64_negative_is_error() {
#[derive(Deserialize)]
struct Cfg {
_val: u64,
}
assert!(from_str::<Cfg>("val = -1\n").is_err());
}
#[test]
fn de_float_field() {
#[derive(Deserialize)]
struct Cfg {
ratio: f64,
}
let cfg: Cfg = from_str("ratio = 1.5\n").unwrap();
assert!((cfg.ratio - 1.5_f64).abs() < f64::EPSILON);
}
#[test]
fn roundtrip_f32() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
ratio: f32,
}
let original = Cfg { ratio: 1.5 };
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert!((original.ratio - roundtripped.ratio).abs() < f32::EPSILON);
}
#[test]
fn roundtrip_f64() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
ratio: f64,
}
let original = Cfg {
ratio: 1.234_567_89,
};
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert!((original.ratio - roundtripped.ratio).abs() < f64::EPSILON);
}
#[test]
fn roundtrip_negative_integer() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
offset: i32,
}
let original = Cfg { offset: -42 };
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn roundtrip_empty_string() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
val: String,
}
let original = Cfg { val: String::new() };
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn roundtrip_option_some() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
val: Option<u32>,
}
let original = Cfg { val: Some(99) };
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn roundtrip_newtype_struct() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Meters(f64);
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
distance: Meters,
}
let original = Cfg {
distance: Meters(1.5),
};
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn roundtrip_tuple_struct() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Point(f64, f64);
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Cfg {
origin: Point,
}
let original = Cfg {
origin: Point(1.0, 2.0),
};
let s = to_string(&original).unwrap();
let roundtripped: Cfg = from_str(&s).unwrap();
assert_eq!(original, roundtripped);
}
#[test]
fn de_unknown_fields_ignored() {
#[derive(Deserialize, PartialEq, Debug)]
struct Cfg {
known: String,
}
let cfg: Cfg = from_str("known = hello\nunknown = world\n").unwrap();
assert_eq!(cfg.known, "hello");
}
#[test]
fn de_bytes_is_unsupported() {
#[derive(Deserialize)]
struct Cfg {
_val: serde_bytes::ByteBuf,
}
assert!(from_str::<Cfg>("val = hello\n").is_err());
}
#[test]
fn ser_bytes_is_unsupported() {
#[derive(Serialize)]
struct Cfg<'a> {
#[serde(with = "serde_bytes")]
val: &'a [u8],
}
assert!(to_string(&Cfg { val: b"hello" }).is_err());
}
#[test]
fn ser_string_with_quote_requiring_quoting_is_error() {
#[derive(Serialize)]
struct Cfg {
expr: String,
}
assert!(matches!(
to_string(&Cfg {
expr: r#"hello "world""#.into(),
}),
Err(serde_structprop::Error::UnsupportedType(_))
));
}
#[test]
fn ser_string_with_braces_and_quote_is_error() {
#[derive(Serialize)]
struct Cfg {
expr: String,
}
assert!(matches!(
to_string(&Cfg {
expr: r#"metric{label=~"$var"}"#.into(),
}),
Err(serde_structprop::Error::UnsupportedType(_))
));
}
#[test]
fn ser_string_with_bare_quote_is_ok() {
#[derive(Serialize)]
struct Cfg {
val: String,
}
let out = to_string(&Cfg {
val: r#"hello"world"#.into(),
})
.unwrap();
assert_eq!(out, "val = hello\"world\n");
}
#[test]
fn ser_string_with_leading_quote_is_error() {
#[derive(Serialize)]
struct Cfg {
val: String,
}
assert!(matches!(
to_string(&Cfg {
val: r#""hello"#.into(),
}),
Err(serde_structprop::Error::UnsupportedType(_))
));
}
#[test]
fn ser_vec_of_structs_exact() {
#[derive(Serialize)]
struct Inner {
x: u32,
y: u32,
}
#[derive(Serialize)]
struct Outer {
items: Vec<Inner>,
}
let o = Outer {
items: vec![Inner { x: 1, y: 2 }, Inner { x: 3, y: 4 }],
};
let out = to_string(&o).unwrap();
assert_eq!(
out, "items = {\n {\n x = 1\n y = 2\n }\n {\n x = 3\n y = 4\n }\n}\n",
"got:\n{out}"
);
}
#[test]
fn roundtrip_vec_of_structs() {
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Inner {
name: String,
value: u32,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Outer {
items: Vec<Inner>,
}
let original = Outer {
items: vec![
Inner {
name: "a".into(),
value: 1,
},
Inner {
name: "b".into(),
value: 2,
},
],
};
let serialized = to_string(&original).unwrap();
let deserialized: Outer = from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn ser_nested_vec_of_structs_indentation() {
#[derive(Serialize)]
struct Member {
name: String,
}
#[derive(Serialize)]
struct Team {
team_name: String,
members: Vec<Member>,
}
let t = Team {
team_name: "eng".into(),
members: vec![
Member {
name: "alice".into(),
},
Member { name: "bob".into() },
],
};
let out = to_string(&t).unwrap();
assert_eq!(
out,
"team_name = eng\nmembers = {\n {\n name = alice\n }\n {\n name = bob\n }\n}\n",
"got:\n{out}"
);
}