• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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