1 use std::fmt;
2
3 use serde::{de, Deserialize};
4 use snapbox::assert_data_eq;
5 use snapbox::prelude::*;
6 use snapbox::str;
7
8 macro_rules! bad {
9 ($toml:expr, $ty:ty, $msg:expr) => {
10 match toml::from_str::<$ty>($toml) {
11 Ok(s) => panic!("parsed to: {:#?}", s),
12 Err(e) => assert_data_eq!(e.to_string(), $msg.raw()),
13 }
14 };
15 }
16
17 #[derive(Debug, Deserialize, PartialEq)]
18 struct Parent<T> {
19 p_a: T,
20 p_b: Vec<Child<T>>,
21 }
22
23 #[derive(Debug, Deserialize, PartialEq)]
24 #[serde(deny_unknown_fields)]
25 struct Child<T> {
26 c_a: T,
27 c_b: T,
28 }
29
30 #[derive(Debug, PartialEq)]
31 enum CasedString {
32 Lowercase(String),
33 Uppercase(String),
34 }
35
36 impl<'de> Deserialize<'de> for CasedString {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: de::Deserializer<'de>,37 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38 where
39 D: de::Deserializer<'de>,
40 {
41 struct CasedStringVisitor;
42
43 impl<'de> de::Visitor<'de> for CasedStringVisitor {
44 type Value = CasedString;
45
46 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 formatter.write_str("a string")
48 }
49
50 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
51 where
52 E: de::Error,
53 {
54 if s.is_empty() {
55 Err(de::Error::invalid_length(0, &"a non-empty string"))
56 } else if s.chars().all(|x| x.is_ascii_lowercase()) {
57 Ok(CasedString::Lowercase(s.to_owned()))
58 } else if s.chars().all(|x| x.is_ascii_uppercase()) {
59 Ok(CasedString::Uppercase(s.to_owned()))
60 } else {
61 Err(de::Error::invalid_value(
62 de::Unexpected::Str(s),
63 &"all lowercase or all uppercase",
64 ))
65 }
66 }
67 }
68
69 deserializer.deserialize_any(CasedStringVisitor)
70 }
71 }
72
73 #[test]
custom_errors()74 fn custom_errors() {
75 toml::from_str::<Parent<CasedString>>(
76 "
77 p_a = 'a'
78 p_b = [{c_a = 'a', c_b = 'c'}]
79 ",
80 )
81 .unwrap();
82
83 // Custom error at p_b value.
84 bad!(
85 "
86 p_a = ''
87 # ^
88 ",
89 Parent<CasedString>,
90 str![[r#"
91 TOML parse error at line 2, column 19
92 |
93 2 | p_a = ''
94 | ^^
95 invalid length 0, expected a non-empty string
96
97 "#]]
98 );
99
100 // Missing field in table.
101 bad!(
102 "
103 p_a = 'a'
104 # ^
105 ",
106 Parent<CasedString>,
107 str![[r#"
108 TOML parse error at line 1, column 1
109 |
110 1 |
111 | ^
112 missing field `p_b`
113
114 "#]]
115 );
116
117 // Invalid type in p_b.
118 bad!(
119 "
120 p_a = 'a'
121 p_b = 1
122 # ^
123 ",
124 Parent<CasedString>,
125 str![[r#"
126 TOML parse error at line 3, column 19
127 |
128 3 | p_b = 1
129 | ^
130 invalid type: integer `1`, expected a sequence
131
132 "#]]
133 );
134
135 // Sub-table in Vec is missing a field.
136 bad!(
137 "
138 p_a = 'a'
139 p_b = [
140 {c_a = 'a'}
141 # ^
142 ]
143 ",
144 Parent<CasedString>,
145 str![[r#"
146 TOML parse error at line 4, column 17
147 |
148 4 | {c_a = 'a'}
149 | ^^^^^^^^^^^
150 missing field `c_b`
151
152 "#]]
153 );
154
155 // Sub-table in Vec has a field with a bad value.
156 bad!(
157 "
158 p_a = 'a'
159 p_b = [
160 {c_a = 'a', c_b = '*'}
161 # ^
162 ]
163 ",
164 Parent<CasedString>,
165 str![[r#"
166 TOML parse error at line 4, column 35
167 |
168 4 | {c_a = 'a', c_b = '*'}
169 | ^^^
170 invalid value: string "*", expected all lowercase or all uppercase
171
172 "#]]
173 );
174
175 // Sub-table in Vec is missing a field.
176 bad!(
177 "
178 p_a = 'a'
179 p_b = [
180 {c_a = 'a', c_b = 'b'},
181 {c_a = 'aa'}
182 # ^
183 ]
184 ",
185 Parent<CasedString>,
186 str![[r#"
187 TOML parse error at line 5, column 17
188 |
189 5 | {c_a = 'aa'}
190 | ^^^^^^^^^^^^
191 missing field `c_b`
192
193 "#]]
194 );
195
196 // Sub-table in the middle of a Vec is missing a field.
197 bad!(
198 "
199 p_a = 'a'
200 p_b = [
201 {c_a = 'a', c_b = 'b'},
202 {c_a = 'aa'},
203 # ^
204 {c_a = 'aaa', c_b = 'bbb'},
205 ]
206 ",
207 Parent<CasedString>,
208 str![[r#"
209 TOML parse error at line 5, column 17
210 |
211 5 | {c_a = 'aa'},
212 | ^^^^^^^^^^^^
213 missing field `c_b`
214
215 "#]]
216 );
217
218 // Sub-table in the middle of a Vec has a field with a bad value.
219 bad!(
220 "
221 p_a = 'a'
222 p_b = [
223 {c_a = 'a', c_b = 'b'},
224 {c_a = 'aa', c_b = 1},
225 # ^
226 {c_a = 'aaa', c_b = 'bbb'},
227 ]
228 ",
229 Parent<CasedString>,
230 str![[r#"
231 TOML parse error at line 5, column 36
232 |
233 5 | {c_a = 'aa', c_b = 1},
234 | ^
235 invalid type: integer `1`, expected a string
236
237 "#]]
238 );
239
240 // Sub-table in the middle of a Vec has an extra field.
241 bad!(
242 "
243 p_a = 'a'
244 p_b = [
245 {c_a = 'a', c_b = 'b'},
246 {c_a = 'aa', c_b = 'bb', c_d = 'd'},
247 # ^
248 {c_a = 'aaa', c_b = 'bbb'},
249 {c_a = 'aaaa', c_b = 'bbbb'},
250 ]
251 ",
252 Parent<CasedString>,
253 str![[r#"
254 TOML parse error at line 5, column 42
255 |
256 5 | {c_a = 'aa', c_b = 'bb', c_d = 'd'},
257 | ^^^
258 unknown field `c_d`, expected `c_a` or `c_b`
259
260 "#]]
261 );
262
263 // Sub-table in the middle of a Vec is missing a field.
264 // FIXME: This location is pretty off.
265 bad!(
266 "
267 p_a = 'a'
268 [[p_b]]
269 c_a = 'a'
270 c_b = 'b'
271 [[p_b]]
272 c_a = 'aa'
273 # c_b = 'bb' # <- missing field
274 [[p_b]]
275 c_a = 'aaa'
276 c_b = 'bbb'
277 [[p_b]]
278 # ^
279 c_a = 'aaaa'
280 c_b = 'bbbb'
281 ",
282 Parent<CasedString>,
283 str![[r#"
284 TOML parse error at line 6, column 13
285 |
286 6 | [[p_b]]
287 | ^^^^^^^
288 missing field `c_b`
289
290 "#]]
291 );
292
293 // Sub-table in the middle of a Vec has a field with a bad value.
294 bad!(
295 "
296 p_a = 'a'
297 [[p_b]]
298 c_a = 'a'
299 c_b = 'b'
300 [[p_b]]
301 c_a = 'aa'
302 c_b = '*'
303 # ^
304 [[p_b]]
305 c_a = 'aaa'
306 c_b = 'bbb'
307 ",
308 Parent<CasedString>,
309 str![[r#"
310 TOML parse error at line 8, column 19
311 |
312 8 | c_b = '*'
313 | ^^^
314 invalid value: string "*", expected all lowercase or all uppercase
315
316 "#]]
317 );
318
319 // Sub-table in the middle of a Vec has an extra field.
320 bad!(
321 "
322 p_a = 'a'
323 [[p_b]]
324 c_a = 'a'
325 c_b = 'b'
326 [[p_b]]
327 c_a = 'aa'
328 c_d = 'dd' # unknown field
329 # ^
330 [[p_b]]
331 c_a = 'aaa'
332 c_b = 'bbb'
333 [[p_b]]
334 c_a = 'aaaa'
335 c_b = 'bbbb'
336 ",
337 Parent<CasedString>,
338 str![[r#"
339 TOML parse error at line 8, column 13
340 |
341 8 | c_d = 'dd' # unknown field
342 | ^^^
343 unknown field `c_d`, expected `c_a` or `c_b`
344
345 "#]]
346 );
347 }
348
349 #[test]
serde_derive_deserialize_errors()350 fn serde_derive_deserialize_errors() {
351 bad!(
352 "
353 p_a = ''
354 # ^
355 ",
356 Parent<String>,
357 str![[r#"
358 TOML parse error at line 1, column 1
359 |
360 1 |
361 | ^
362 missing field `p_b`
363
364 "#]]
365 );
366
367 bad!(
368 "
369 p_a = ''
370 p_b = [
371 {c_a = ''}
372 # ^
373 ]
374 ",
375 Parent<String>,
376 str![[r#"
377 TOML parse error at line 4, column 17
378 |
379 4 | {c_a = ''}
380 | ^^^^^^^^^^
381 missing field `c_b`
382
383 "#]]
384 );
385
386 bad!(
387 "
388 p_a = ''
389 p_b = [
390 {c_a = '', c_b = 1}
391 # ^
392 ]
393 ",
394 Parent<String>,
395 str![[r#"
396 TOML parse error at line 4, column 34
397 |
398 4 | {c_a = '', c_b = 1}
399 | ^
400 invalid type: integer `1`, expected a string
401
402 "#]]
403 );
404
405 // FIXME: This location could be better.
406 bad!(
407 "
408 p_a = ''
409 p_b = [
410 {c_a = '', c_b = '', c_d = ''},
411 # ^
412 ]
413 ",
414 Parent<String>,
415 str![[r#"
416 TOML parse error at line 4, column 38
417 |
418 4 | {c_a = '', c_b = '', c_d = ''},
419 | ^^^
420 unknown field `c_d`, expected `c_a` or `c_b`
421
422 "#]]
423 );
424
425 bad!(
426 "
427 p_a = 'a'
428 p_b = [
429 {c_a = '', c_b = 1, c_d = ''},
430 # ^
431 ]
432 ",
433 Parent<String>,
434 str![[r#"
435 TOML parse error at line 4, column 34
436 |
437 4 | {c_a = '', c_b = 1, c_d = ''},
438 | ^
439 invalid type: integer `1`, expected a string
440
441 "#]]
442 );
443 }
444
445 #[test]
error_handles_crlf()446 fn error_handles_crlf() {
447 bad!(
448 "\r\n\
449 [t1]\r\n\
450 [t2]\r\n\
451 a = 1\r\n\
452 a = 2\r\n\
453 ",
454 toml::Value,
455 str![[r#"
456 TOML parse error at line 5, column 1
457 |
458 5 | a = 2
459 | ^
460 duplicate key `a` in table `t2`
461
462 "#]]
463 );
464
465 // Should be the same as above.
466 bad!(
467 "\n\
468 [t1]\n\
469 [t2]\n\
470 a = 1\n\
471 a = 2\n\
472 ",
473 toml::Value,
474 str![[r#"
475 TOML parse error at line 5, column 1
476 |
477 5 | a = 2
478 | ^
479 duplicate key `a` in table `t2`
480
481 "#]]
482 );
483 }
484