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