1#![allow(clippy::too_many_lines)]
2
3use serde::{de, Deserialize};
4use std::fmt;
5
6macro_rules! bad {
7 ($toml:expr, $ty:ty, $msg:expr) => {
8 match basic_toml::from_str::<$ty>($toml) {
9 Ok(s) => panic!("parsed to: {:#?}", s),
10 Err(e) => assert_eq!(e.to_string(), $msg),
11 }
12 };
13}
14
15#[derive(Debug, Deserialize, PartialEq)]
16struct Parent<T> {
17 p_a: T,
18 p_b: Vec<Child<T>>,
19}
20
21#[derive(Debug, Deserialize, PartialEq)]
22#[serde(deny_unknown_fields)]
23struct Child<T> {
24 c_a: T,
25 c_b: T,
26}
27
28#[derive(Debug, PartialEq)]
29enum CasedString {
30 Lowercase(String),
31 Uppercase(String),
32}
33
34impl<'de> de::Deserialize<'de> for CasedString {
35 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36 where
37 D: de::Deserializer<'de>,
38 {
39 struct CasedStringVisitor;
40
41 impl<'de> de::Visitor<'de> for CasedStringVisitor {
42 type Value = CasedString;
43
44 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
45 formatter.write_str("a string")
46 }
47
48 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
49 where
50 E: de::Error,
51 {
52 if s.is_empty() {
53 Err(de::Error::invalid_length(0, &"a non-empty string"))
54 } else if s.chars().all(|x| x.is_ascii_lowercase()) {
55 Ok(CasedString::Lowercase(s.to_string()))
56 } else if s.chars().all(|x| x.is_ascii_uppercase()) {
57 Ok(CasedString::Uppercase(s.to_string()))
58 } else {
59 Err(de::Error::invalid_value(
60 de::Unexpected::Str(s),
61 &"all lowercase or all uppercase",
62 ))
63 }
64 }
65 }
66
67 deserializer.deserialize_any(CasedStringVisitor)
68 }
69}
70
71#[test]
72fn custom_errors() {
73 basic_toml::from_str::<Parent<CasedString>>(
74 "
75 p_a = 'a'
76 p_b = [{c_a = 'a', c_b = 'c'}]
77 ",
78 )
79 .unwrap();
80
81 // Custom error at p_b value.
82 bad!(
83 "
84 p_a = ''
85 # ^
86 ",
87 Parent<CasedString>,
88 "invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19"
89 );
90
91 // Missing field in table.
92 bad!(
93 "
94 p_a = 'a'
95 # ^
96 ",
97 Parent<CasedString>,
98 "missing field `p_b` at line 1 column 1"
99 );
100
101 // Invalid type in p_b.
102 bad!(
103 "
104 p_a = 'a'
105 p_b = 1
106 # ^
107 ",
108 Parent<CasedString>,
109 "invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19"
110 );
111
112 // Sub-table in Vec is missing a field.
113 bad!(
114 "
115 p_a = 'a'
116 p_b = [
117 {c_a = 'a'}
118 # ^
119 ]
120 ",
121 Parent<CasedString>,
122 "missing field `c_b` for key `p_b` at line 4 column 17"
123 );
124
125 // Sub-table in Vec has a field with a bad value.
126 bad!(
127 "
128 p_a = 'a'
129 p_b = [
130 {c_a = 'a', c_b = '*'}
131 # ^
132 ]
133 ",
134 Parent<CasedString>,
135 "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b` at line 4 column 35"
136 );
137
138 // Sub-table in Vec is missing a field.
139 bad!(
140 "
141 p_a = 'a'
142 p_b = [
143 {c_a = 'a', c_b = 'b'},
144 {c_a = 'aa'}
145 # ^
146 ]
147 ",
148 Parent<CasedString>,
149 "missing field `c_b` for key `p_b` at line 5 column 17"
150 );
151
152 // Sub-table in the middle of a Vec is missing a field.
153 bad!(
154 "
155 p_a = 'a'
156 p_b = [
157 {c_a = 'a', c_b = 'b'},
158 {c_a = 'aa'},
159 # ^
160 {c_a = 'aaa', c_b = 'bbb'},
161 ]
162 ",
163 Parent<CasedString>,
164 "missing field `c_b` for key `p_b` at line 5 column 17"
165 );
166
167 // Sub-table in the middle of a Vec has a field with a bad value.
168 bad!(
169 "
170 p_a = 'a'
171 p_b = [
172 {c_a = 'a', c_b = 'b'},
173 {c_a = 'aa', c_b = 1},
174 # ^
175 {c_a = 'aaa', c_b = 'bbb'},
176 ]
177 ",
178 Parent<CasedString>,
179 "invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36"
180 );
181
182 // Sub-table in the middle of a Vec has an extra field.
183 // FIXME: This location could be better.
184 bad!(
185 "
186 p_a = 'a'
187 p_b = [
188 {c_a = 'a', c_b = 'b'},
189 {c_a = 'aa', c_b = 'bb', c_d = 'd'},
190 # ^
191 {c_a = 'aaa', c_b = 'bbb'},
192 {c_a = 'aaaa', c_b = 'bbbb'},
193 ]
194 ",
195 Parent<CasedString>,
196 "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17"
197 );
198
199 // Sub-table in the middle of a Vec is missing a field.
200 // FIXME: This location is pretty off.
201 bad!(
202 "
203 p_a = 'a'
204 [[p_b]]
205 c_a = 'a'
206 c_b = 'b'
207 [[p_b]]
208 c_a = 'aa'
209 # c_b = 'bb' # <- missing field
210 [[p_b]]
211 c_a = 'aaa'
212 c_b = 'bbb'
213 [[p_b]]
214 # ^
215 c_a = 'aaaa'
216 c_b = 'bbbb'
217 ",
218 Parent<CasedString>,
219 "missing field `c_b` for key `p_b` at line 12 column 13"
220 );
221
222 // Sub-table in the middle of a Vec has a field with a bad value.
223 bad!(
224 "
225 p_a = 'a'
226 [[p_b]]
227 c_a = 'a'
228 c_b = 'b'
229 [[p_b]]
230 c_a = 'aa'
231 c_b = '*'
232 # ^
233 [[p_b]]
234 c_a = 'aaa'
235 c_b = 'bbb'
236 ",
237 Parent<CasedString>,
238 "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19"
239 );
240
241 // Sub-table in the middle of a Vec has an extra field.
242 // FIXME: This location is pretty off.
243 bad!(
244 "
245 p_a = 'a'
246 [[p_b]]
247 c_a = 'a'
248 c_b = 'b'
249 [[p_b]]
250 c_a = 'aa'
251 c_d = 'dd' # unknown field
252 [[p_b]]
253 c_a = 'aaa'
254 c_b = 'bbb'
255 [[p_b]]
256 # ^
257 c_a = 'aaaa'
258 c_b = 'bbbb'
259 ",
260 Parent<CasedString>,
261 "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13"
262 );
263}
264
265#[test]
266fn serde_derive_deserialize_errors() {
267 bad!(
268 "
269 p_a = ''
270 # ^
271 ",
272 Parent<String>,
273 "missing field `p_b` at line 1 column 1"
274 );
275
276 bad!(
277 "
278 p_a = ''
279 p_b = [
280 {c_a = ''}
281 # ^
282 ]
283 ",
284 Parent<String>,
285 "missing field `c_b` for key `p_b` at line 4 column 17"
286 );
287
288 bad!(
289 "
290 p_a = ''
291 p_b = [
292 {c_a = '', c_b = 1}
293 # ^
294 ]
295 ",
296 Parent<String>,
297 "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
298 );
299
300 // FIXME: This location could be better.
301 bad!(
302 "
303 p_a = ''
304 p_b = [
305 {c_a = '', c_b = '', c_d = ''},
306 # ^
307 ]
308 ",
309 Parent<String>,
310 "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17"
311 );
312
313 bad!(
314 "
315 p_a = 'a'
316 p_b = [
317 {c_a = '', c_b = 1, c_d = ''},
318 # ^
319 ]
320 ",
321 Parent<String>,
322 "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
323 );
324}
325
326#[test]
327fn error_handles_crlf() {
328 bad!(
329 "\r\n\
330 [t1]\r\n\
331 [t2]\r\n\
332 a = 1\r\n\
333 . = 2\r\n\
334 ",
335 serde_json::Value,
336 "expected a table key, found a period at line 5 column 1"
337 );
338
339 // Should be the same as above.
340 bad!(
341 "\n\
342 [t1]\n\
343 [t2]\n\
344 a = 1\n\
345 . = 2\n\
346 ",
347 serde_json::Value,
348 "expected a table key, found a period at line 5 column 1"
349 );
350}
351