1#![allow(clippy::let_underscore_untyped, clippy::uninlined_format_args)]
2
3use serde_json::{json, Value};
4
5macro_rules! bad {
6 ($toml:expr, $msg:expr) => {
7 match basic_toml::from_str::<Value>($toml) {
8 Ok(s) => panic!("parsed to: {:#?}", s),
9 Err(e) => assert_eq!(e.to_string(), $msg),
10 }
11 };
12}
13
14#[test]
15fn crlf() {
16 let toml = "\
17 [project]\r\n\
18 \r\n\
19 name = \"splay\"\r\n\
20 version = \"0.1.0\"\r\n\
21 authors = [\"alex@crichton.co\"]\r\n\
22 \r\n\
23 [[lib]]\r\n\
24 \r\n\
25 path = \"lib.rs\"\r\n\
26 name = \"splay\"\r\n\
27 description = \"\"\"\
28 A Rust implementation of a TAR file reader and writer. This library does not\r\n\
29 currently handle compression, but it is abstract over all I/O readers and\r\n\
30 writers. Additionally, great lengths are taken to ensure that the entire\r\n\
31 contents are never required to be entirely resident in memory all at once.\r\n\
32 \"\"\"\
33 ";
34 basic_toml::from_str::<Value>(toml).unwrap();
35}
36
37#[test]
38fn fun_with_strings() {
39 let toml = r#"
40bar = "\U00000000"
41key1 = "One\nTwo"
42key2 = """One\nTwo"""
43key3 = """
44One
45Two"""
46
47key4 = "The quick brown fox jumps over the lazy dog."
48key5 = """
49The quick brown \
50
51
52fox jumps over \
53the lazy dog."""
54key6 = """\
55 The quick brown \
56 fox jumps over \
57 the lazy dog.\
58 """
59# What you see is what you get.
60winpath = 'C:\Users\nodejs\templates'
61winpath2 = '\\ServerX\admin$\system32\'
62quoted = 'Tom "Dubs" Preston-Werner'
63regex = '<\i\c*\s*>'
64
65regex2 = '''I [dw]on't need \d{2} apples'''
66lines = '''
67The first newline is
68trimmed in raw strings.
69All other whitespace
70is preserved.
71'''
72"#;
73 let table: Value = basic_toml::from_str(toml).unwrap();
74 assert_eq!(table["bar"], json!("\0"));
75 assert_eq!(table["key1"], json!("One\nTwo"));
76 assert_eq!(table["key2"], json!("One\nTwo"));
77 assert_eq!(table["key3"], json!("One\nTwo"));
78
79 let msg = "The quick brown fox jumps over the lazy dog.";
80 assert_eq!(table["key4"], json!(msg));
81 assert_eq!(table["key5"], json!(msg));
82 assert_eq!(table["key6"], json!(msg));
83
84 assert_eq!(table["winpath"], json!(r"C:\Users\nodejs\templates"));
85 assert_eq!(table["winpath2"], json!(r"\\ServerX\admin$\system32\"));
86 assert_eq!(table["quoted"], json!(r#"Tom "Dubs" Preston-Werner"#));
87 assert_eq!(table["regex"], json!(r"<\i\c*\s*>"));
88 assert_eq!(table["regex2"], json!(r"I [dw]on't need \d{2} apples"));
89 assert_eq!(
90 table["lines"],
91 json!(
92 "The first newline is\n\
93 trimmed in raw strings.\n\
94 All other whitespace\n\
95 is preserved.\n"
96 )
97 );
98}
99
100#[test]
101fn tables_in_arrays() {
102 let toml = "
103[[foo]]
104#…
105[foo.bar]
106#…
107
108[[foo]] # ...
109#…
110[foo.bar]
111#...
112";
113 let table: Value = basic_toml::from_str(toml).unwrap();
114 table["foo"][0]["bar"].as_object().unwrap();
115 table["foo"][1]["bar"].as_object().unwrap();
116}
117
118#[test]
119fn empty_table() {
120 let toml = "
121[foo]";
122 let table: Value = basic_toml::from_str(toml).unwrap();
123 table["foo"].as_object().unwrap();
124}
125
126#[test]
127fn fruit() {
128 let toml = r#"
129[[fruit]]
130name = "apple"
131
132[fruit.physical]
133color = "red"
134shape = "round"
135
136[[fruit.variety]]
137name = "red delicious"
138
139[[fruit.variety]]
140name = "granny smith"
141
142[[fruit]]
143name = "banana"
144
145[[fruit.variety]]
146name = "plantain"
147"#;
148 let table: Value = basic_toml::from_str(toml).unwrap();
149 assert_eq!(table["fruit"][0]["name"], json!("apple"));
150 assert_eq!(table["fruit"][0]["physical"]["color"], json!("red"));
151 assert_eq!(table["fruit"][0]["physical"]["shape"], json!("round"));
152 assert_eq!(
153 table["fruit"][0]["variety"][0]["name"],
154 json!("red delicious")
155 );
156 assert_eq!(
157 table["fruit"][0]["variety"][1]["name"],
158 json!("granny smith")
159 );
160 assert_eq!(table["fruit"][1]["name"], json!("banana"));
161 assert_eq!(table["fruit"][1]["variety"][0]["name"], json!("plantain"));
162}
163
164#[test]
165fn stray_cr() {
166 bad!("\r", "unexpected character found: `\\r` at line 1 column 1");
167 bad!(
168 "a = [ \r ]",
169 "unexpected character found: `\\r` at line 1 column 7"
170 );
171 bad!(
172 "a = \"\"\"\r\"\"\"",
173 "invalid character in string: `\\r` at line 1 column 8"
174 );
175 bad!(
176 "a = \"\"\"\\ \r \"\"\"",
177 "invalid escape character in string: ` ` at line 1 column 9"
178 );
179 bad!(
180 "a = '''\r'''",
181 "invalid character in string: `\\r` at line 1 column 8"
182 );
183 bad!(
184 "a = '\r'",
185 "invalid character in string: `\\r` at line 1 column 6"
186 );
187 bad!(
188 "a = \"\r\"",
189 "invalid character in string: `\\r` at line 1 column 6"
190 );
191}
192
193#[test]
194fn blank_literal_string() {
195 let table: Value = basic_toml::from_str("foo = ''").unwrap();
196 assert_eq!(table["foo"], json!(""));
197}
198
199#[test]
200fn many_blank() {
201 let table: Value = basic_toml::from_str("foo = \"\"\"\n\n\n\"\"\"").unwrap();
202 assert_eq!(table["foo"], json!("\n\n"));
203}
204
205#[test]
206fn literal_eats_crlf() {
207 let toml = "
208 foo = \"\"\"\\\r\n\"\"\"
209 bar = \"\"\"\\\r\n \r\n \r\n a\"\"\"
210 ";
211 let table: Value = basic_toml::from_str(toml).unwrap();
212 assert_eq!(table["foo"], json!(""));
213 assert_eq!(table["bar"], json!("a"));
214}
215
216#[test]
217fn string_no_newline() {
218 bad!("a = \"\n\"", "newline in string found at line 1 column 6");
219 bad!("a = '\n'", "newline in string found at line 1 column 6");
220}
221
222#[test]
223fn bad_leading_zeros() {
224 bad!("a = 00", "invalid number at line 1 column 6");
225 bad!("a = -00", "invalid number at line 1 column 7");
226 bad!("a = +00", "invalid number at line 1 column 7");
227 bad!("a = 00.0", "invalid number at line 1 column 6");
228 bad!("a = -00.0", "invalid number at line 1 column 7");
229 bad!("a = +00.0", "invalid number at line 1 column 7");
230 bad!(
231 "a = 9223372036854775808",
232 "invalid number at line 1 column 5"
233 );
234 bad!(
235 "a = -9223372036854775809",
236 "invalid number at line 1 column 5"
237 );
238}
239
240#[test]
241fn bad_floats() {
242 bad!("a = 0.", "invalid number at line 1 column 7");
243 bad!("a = 0.e", "invalid number at line 1 column 7");
244 bad!("a = 0.E", "invalid number at line 1 column 7");
245 bad!("a = 0.0E", "invalid number at line 1 column 5");
246 bad!("a = 0.0e", "invalid number at line 1 column 5");
247 bad!("a = 0.0e-", "invalid number at line 1 column 9");
248 bad!("a = 0.0e+", "invalid number at line 1 column 5");
249}
250
251#[test]
252fn floats() {
253 macro_rules! t {
254 ($actual:expr, $expected:expr) => {{
255 let f = format!("foo = {}", $actual);
256 println!("{}", f);
257 let a: Value = basic_toml::from_str(&f).unwrap();
258 assert_eq!(a["foo"], json!($expected));
259 }};
260 }
261
262 t!("1.0", 1.0);
263 t!("1.0e0", 1.0);
264 t!("1.0e+0", 1.0);
265 t!("1.0e-0", 1.0);
266 t!("1E-0", 1.0);
267 t!("1.001e-0", 1.001);
268 t!("2e10", 2e10);
269 t!("2e+10", 2e10);
270 t!("2e-10", 2e-10);
271 t!("2_0.0", 20.0);
272 t!("2_0.0_0e1_0", 20.0e10);
273 t!("2_0.1_0e1_0", 20.1e10);
274}
275
276#[test]
277fn bare_key_names() {
278 let toml = "
279 foo = 3
280 foo_3 = 3
281 foo_-2--3--r23f--4-f2-4 = 3
282 _ = 3
283 - = 3
284 8 = 8
285 \"a\" = 3
286 \"!\" = 3
287 \"a^b\" = 3
288 \"\\\"\" = 3
289 \"character encoding\" = \"value\"
290 'ʎǝʞ' = \"value\"
291 ";
292 let a: Value = basic_toml::from_str(toml).unwrap();
293 let _ = &a["foo"];
294 let _ = &a["-"];
295 let _ = &a["_"];
296 let _ = &a["8"];
297 let _ = &a["foo_3"];
298 let _ = &a["foo_-2--3--r23f--4-f2-4"];
299 let _ = &a["a"];
300 let _ = &a["!"];
301 let _ = &a["\""];
302 let _ = &a["character encoding"];
303 let _ = &a["ʎǝʞ"];
304}
305
306#[test]
307fn bad_keys() {
308 bad!(
309 "key\n=3",
310 "expected an equals, found a newline at line 1 column 4"
311 );
312 bad!(
313 "key=\n3",
314 "expected a value, found a newline at line 1 column 5"
315 );
316 bad!(
317 "key|=3",
318 "unexpected character found: `|` at line 1 column 4"
319 );
320 bad!(
321 "=3",
322 "expected a table key, found an equals at line 1 column 1"
323 );
324 bad!(
325 "\"\"|=3",
326 "unexpected character found: `|` at line 1 column 3"
327 );
328 bad!("\"\n\"|=3", "newline in string found at line 1 column 2");
329 bad!(
330 "\"\r\"|=3",
331 "invalid character in string: `\\r` at line 1 column 2"
332 );
333 bad!(
334 "''''''=3",
335 "multiline strings are not allowed for key at line 1 column 1"
336 );
337 bad!(
338 "\"\"\"\"\"\"=3",
339 "multiline strings are not allowed for key at line 1 column 1"
340 );
341 bad!(
342 "'''key'''=3",
343 "multiline strings are not allowed for key at line 1 column 1"
344 );
345 bad!(
346 "\"\"\"key\"\"\"=3",
347 "multiline strings are not allowed for key at line 1 column 1"
348 );
349}
350
351#[test]
352fn bad_table_names() {
353 bad!(
354 "[]",
355 "expected a table key, found a right bracket at line 1 column 2"
356 );
357 bad!(
358 "[.]",
359 "expected a table key, found a period at line 1 column 2"
360 );
361 bad!(
362 "[a.]",
363 "expected a table key, found a right bracket at line 1 column 4"
364 );
365 bad!("[!]", "unexpected character found: `!` at line 1 column 2");
366 bad!("[\"\n\"]", "newline in string found at line 1 column 3");
367 bad!(
368 "[a.b]\n[a.\"b\"]",
369 "redefinition of table `a.b` for key `a.b` at line 2 column 1"
370 );
371 bad!("[']", "unterminated string at line 1 column 2");
372 bad!("[''']", "unterminated string at line 1 column 2");
373 bad!(
374 "['''''']",
375 "multiline strings are not allowed for key at line 1 column 2"
376 );
377 bad!(
378 "['''foo''']",
379 "multiline strings are not allowed for key at line 1 column 2"
380 );
381 bad!(
382 "[\"\"\"bar\"\"\"]",
383 "multiline strings are not allowed for key at line 1 column 2"
384 );
385 bad!("['\n']", "newline in string found at line 1 column 3");
386 bad!("['\r\n']", "newline in string found at line 1 column 3");
387}
388
389#[test]
390fn table_names() {
391 let toml = "
392 [a.\"b\"]
393 [\"f f\"]
394 [\"f.f\"]
395 [\"\\\"\"]
396 ['a.a']
397 ['\"\"']
398 ";
399 let a: Value = basic_toml::from_str(toml).unwrap();
400 println!("{:?}", a);
401 let _ = &a["a"]["b"];
402 let _ = &a["f f"];
403 let _ = &a["f.f"];
404 let _ = &a["\""];
405 let _ = &a["\"\""];
406}
407
408#[test]
409fn invalid_bare_numeral() {
410 bad!("4", "expected an equals, found eof at line 1 column 2");
411}
412
413#[test]
414fn inline_tables() {
415 basic_toml::from_str::<Value>("a = {}").unwrap();
416 basic_toml::from_str::<Value>("a = {b=1}").unwrap();
417 basic_toml::from_str::<Value>("a = { b = 1 }").unwrap();
418 basic_toml::from_str::<Value>("a = {a=1,b=2}").unwrap();
419 basic_toml::from_str::<Value>("a = {a=1,b=2,c={}}").unwrap();
420
421 bad!(
422 "a = {a=1,}",
423 "expected a table key, found a right brace at line 1 column 10"
424 );
425 bad!(
426 "a = {,}",
427 "expected a table key, found a comma at line 1 column 6"
428 );
429 bad!(
430 "a = {a=1,a=1}",
431 "duplicate key: `a` for key `a` at line 1 column 10"
432 );
433 bad!(
434 "a = {\n}",
435 "expected a table key, found a newline at line 1 column 6"
436 );
437 bad!(
438 "a = {",
439 "expected a table key, found eof at line 1 column 6"
440 );
441
442 basic_toml::from_str::<Value>("a = {a=[\n]}").unwrap();
443 basic_toml::from_str::<Value>("a = {\"a\"=[\n]}").unwrap();
444 basic_toml::from_str::<Value>("a = [\n{},\n{},\n]").unwrap();
445}
446
447#[test]
448fn number_underscores() {
449 macro_rules! t {
450 ($actual:expr, $expected:expr) => {{
451 let f = format!("foo = {}", $actual);
452 let table: Value = basic_toml::from_str(&f).unwrap();
453 assert_eq!(table["foo"], json!($expected));
454 }};
455 }
456
457 t!("1_0", 10);
458 t!("1_0_0", 100);
459 t!("1_000", 1000);
460 t!("+1_000", 1000);
461 t!("-1_000", -1000);
462}
463
464#[test]
465fn bad_underscores() {
466 bad!("foo = 0_", "invalid number at line 1 column 7");
467 bad!("foo = 0__0", "invalid number at line 1 column 7");
468 bad!(
469 "foo = __0",
470 "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
471 );
472 bad!("foo = 1_0_", "invalid number at line 1 column 7");
473}
474
475#[test]
476fn bad_unicode_codepoint() {
477 bad!(
478 "foo = \"\\uD800\"",
479 "invalid escape value: `55296` at line 1 column 9"
480 );
481}
482
483#[test]
484fn bad_strings() {
485 bad!(
486 "foo = \"\\uxx\"",
487 "invalid hex escape character in string: `x` at line 1 column 10"
488 );
489 bad!(
490 "foo = \"\\u\"",
491 "invalid hex escape character in string: `\\\"` at line 1 column 10"
492 );
493 bad!("foo = \"\\", "unterminated string at line 1 column 7");
494 bad!("foo = '", "unterminated string at line 1 column 7");
495}
496
497#[test]
498fn empty_string() {
499 let table: Value = basic_toml::from_str::<Value>("foo = \"\"").unwrap();
500 assert_eq!(table["foo"], json!(""));
501}
502
503#[test]
504fn booleans() {
505 let table: Value = basic_toml::from_str("foo = true").unwrap();
506 assert_eq!(table["foo"], json!(true));
507
508 let table: Value = basic_toml::from_str("foo = false").unwrap();
509 assert_eq!(table["foo"], json!(false));
510
511 bad!(
512 "foo = true2",
513 "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
514 );
515 bad!(
516 "foo = false2",
517 "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
518 );
519 bad!(
520 "foo = t1",
521 "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
522 );
523 bad!(
524 "foo = f2",
525 "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
526 );
527}
528
529#[test]
530fn bad_nesting() {
531 bad!(
532 "
533 a = [2]
534 [[a]]
535 b = 5
536 ",
537 "duplicate key: `a` at line 3 column 11"
538 );
539 bad!(
540 "
541 a = 1
542 [a.b]
543 ",
544 "duplicate key: `a` at line 3 column 10"
545 );
546 bad!(
547 "
548 a = []
549 [a.b]
550 ",
551 "duplicate key: `a` at line 3 column 10"
552 );
553 bad!(
554 "
555 a = []
556 [[a.b]]
557 ",
558 "duplicate key: `a` at line 3 column 11"
559 );
560 bad!(
561 "
562 [a]
563 b = { c = 2, d = {} }
564 [a.b]
565 c = 2
566 ",
567 "duplicate key: `b` for key `a` at line 4 column 12"
568 );
569}
570
571#[test]
572fn bad_table_redefine() {
573 bad!(
574 "
575 [a]
576 foo=\"bar\"
577 [a.b]
578 foo=\"bar\"
579 [a]
580 ",
581 "redefinition of table `a` for key `a` at line 6 column 9"
582 );
583 bad!(
584 "
585 [a]
586 foo=\"bar\"
587 b = { foo = \"bar\" }
588 [a]
589 ",
590 "redefinition of table `a` for key `a` at line 5 column 9"
591 );
592 bad!(
593 "
594 [a]
595 b = {}
596 [a.b]
597 ",
598 "duplicate key: `b` for key `a` at line 4 column 12"
599 );
600
601 bad!(
602 "
603 [a]
604 b = {}
605 [a]
606 ",
607 "redefinition of table `a` for key `a` at line 4 column 9"
608 );
609}
610
611#[test]
612fn datetimes() {
613 bad!(
614 "foo = 2016-09-09T09:09:09Z",
615 "invalid number at line 1 column 7"
616 );
617 bad!(
618 "foo = 2016-09-09T09:09:09.1Z",
619 "invalid number at line 1 column 7"
620 );
621 bad!(
622 "foo = 2016-09-09T09:09:09.2+10:00",
623 "invalid number at line 1 column 7"
624 );
625 bad!(
626 "foo = 2016-09-09T09:09:09.123456789-02:00",
627 "invalid number at line 1 column 7"
628 );
629 bad!(
630 "foo = 2016-09-09T09:09:09.Z",
631 "invalid number at line 1 column 7"
632 );
633 bad!(
634 "foo = 2016-9-09T09:09:09Z",
635 "invalid number at line 1 column 7"
636 );
637 bad!(
638 "foo = 2016-09-09T09:09:09+2:00",
639 "invalid number at line 1 column 7"
640 );
641 bad!(
642 "foo = 2016-09-09T09:09:09-2:00",
643 "invalid number at line 1 column 7"
644 );
645 bad!(
646 "foo = 2016-09-09T09:09:09Z-2:00",
647 "invalid number at line 1 column 7"
648 );
649}
650
651#[test]
652fn require_newline_after_value() {
653 bad!("0=0r=false", "invalid number at line 1 column 3");
654 bad!(
655 r#"
6560=""o=""m=""r=""00="0"q="""0"""e="""0"""
657"#,
658 "expected newline, found an identifier at line 2 column 5"
659 );
660 bad!(
661 r#"
662[[0000l0]]
6630="0"[[0000l0]]
6640="0"[[0000l0]]
6650="0"l="0"
666"#,
667 "expected newline, found a left bracket at line 3 column 6"
668 );
669 bad!(
670 r#"
6710=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
672"#,
673 "expected newline, found an identifier at line 2 column 6"
674 );
675 bad!(
676 "
6770=0r0=0r=false
678",
679 "invalid number at line 2 column 3"
680 );
681 bad!(
682 "
6830=0r0=0r=falsefal=false
684",
685 "invalid number at line 2 column 3"
686 );
687}
688