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