1mod de;
38mod error;
39mod ser;
40
41pub use de::from_str;
42pub use error::{Error, Result};
43pub use ser::to_string;
44
45#[cfg(test)]
46mod tests {
47 use serde::{Deserialize, Serialize};
48
49 use crate::{Error, from_str, to_string};
50
51 #[derive(Serialize, Deserialize, PartialEq, Debug)]
52 struct Record {
53 foo: String,
54 baz: u64,
55 cool: bool,
56 message: String,
57 }
58
59 #[test]
60 fn basic_deserialize() {
61 let line = r#"foo=bar baz=42 cool=true message="hello world""#;
62 let got: Record = from_str(line).unwrap();
63 assert_eq!(
64 got,
65 Record {
66 foo: "bar".into(),
67 baz: 42,
68 cool: true,
69 message: "hello world".into(),
70 }
71 );
72 }
73
74 #[test]
75 fn round_trip_is_canonical() {
76 let record = Record {
77 foo: "bar".into(),
78 baz: 42,
79 cool: true,
80 message: "hello world".into(),
81 };
82 let line = to_string(&record).unwrap();
83 assert_eq!(line, r#"foo=bar baz=42 cool=true message="hello world""#);
84 assert_eq!(from_str::<Record>(&line).unwrap(), record);
85 }
86
87 #[test]
88 fn type_decides_interpretation() {
89 #[derive(Deserialize, PartialEq, Debug)]
90 struct AsString {
91 x: String,
92 }
93 #[derive(Deserialize, PartialEq, Debug)]
94 struct AsInt {
95 x: u64,
96 }
97
98 assert_eq!(from_str::<AsString>("x=42").unwrap().x, "42");
99 assert_eq!(from_str::<AsInt>("x=42").unwrap().x, 42);
100 assert_eq!(from_str::<AsInt>(r#"x="42""#).unwrap().x, 42);
102 assert_eq!(from_str::<AsString>(r#"x="42""#).unwrap().x, "42");
103 }
104
105 #[derive(Serialize, Deserialize, PartialEq, Debug)]
106 struct WithOption {
107 a: u32,
108 b: Option<String>,
109 }
110
111 #[test]
112 fn option_present_and_missing() {
113 assert_eq!(
114 from_str::<WithOption>("a=1 b=hi").unwrap(),
115 WithOption { a: 1, b: Some("hi".into()) }
116 );
117 assert_eq!(
118 from_str::<WithOption>("a=1").unwrap(),
119 WithOption { a: 1, b: None }
120 );
121 }
122
123 #[test]
124 fn option_serialization_skips_none() {
125 assert_eq!(to_string(&WithOption { a: 1, b: None }).unwrap(), "a=1");
126 assert_eq!(
127 to_string(&WithOption { a: 1, b: Some("x".into()) }).unwrap(),
128 "a=1 b=x"
129 );
130 }
131
132 #[test]
133 fn unknown_keys_are_ignored() {
134 #[derive(Deserialize, PartialEq, Debug)]
135 struct OnlyA {
136 a: u32,
137 }
138 let got: OnlyA = from_str(r#"a=1 extra=zzz junk="a b""#).unwrap();
139 assert_eq!(got, OnlyA { a: 1 });
140 }
141
142 #[test]
143 fn quoting_and_escaping_round_trip() {
144 #[derive(Serialize, Deserialize, PartialEq, Debug)]
145 struct M {
146 m: String,
147 }
148 let input = r#"m="he said \"hi\" \\ ok""#;
149 let got: M = from_str(input).unwrap();
150 assert_eq!(got.m, r#"he said "hi" \ ok"#);
151 assert_eq!(to_string(&got).unwrap(), input);
152 }
153
154 #[test]
155 fn empty_value_round_trips() {
156 #[derive(Serialize, Deserialize, PartialEq, Debug)]
157 struct E {
158 a: String,
159 }
160 assert_eq!(from_str::<E>("a=").unwrap().a, "");
161 assert_eq!(from_str::<E>(r#"a="""#).unwrap().a, "");
162 assert_eq!(to_string(&E { a: String::new() }).unwrap(), r#"a="""#);
163 }
164
165 #[test]
166 fn char_and_numbers() {
167 #[derive(Deserialize, PartialEq, Debug)]
168 struct C {
169 c: char,
170 n: i32,
171 f: f64,
172 }
173 let got: C = from_str("c=x n=-5 f=2.5").unwrap();
174 assert_eq!(got, C { c: 'x', n: -5, f: 2.5 });
175 }
176
177 #[test]
178 fn error_cases() {
179 #[derive(Deserialize, Debug)]
180 struct N {
181 #[allow(dead_code)]
182 n: u64,
183 }
184 #[derive(Deserialize, Debug)]
185 struct B {
186 #[allow(dead_code)]
187 cool: bool,
188 }
189 #[derive(Deserialize, Debug)]
190 struct Ch {
191 #[allow(dead_code)]
192 c: char,
193 }
194
195 assert_eq!(from_str::<N>("n=abc").unwrap_err(), Error::ParseInt("abc".into()));
196 assert_eq!(
197 from_str::<B>("cool=yes").unwrap_err(),
198 Error::ParseBool("yes".into())
199 );
200 assert_eq!(
201 from_str::<Ch>("c=xy").unwrap_err(),
202 Error::ParseChar("xy".into())
203 );
204 assert_eq!(from_str::<N>("n").unwrap_err(), Error::ExpectedEquals);
205 }
206
207 #[test]
208 fn top_level_guard() {
209 assert_eq!(from_str::<u64>("a=1").unwrap_err(), Error::TopLevelNotMap);
210 assert_eq!(to_string(&5u64).unwrap_err(), Error::TopLevelNotMap);
211 }
212}