aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2017-01-29 16:53:20 -0800
committerAlex Crichton <alex@alexcrichton.com>2017-02-08 21:21:18 -0800
commitf66d8bcf33530c858a502bfa170f2383a8cbc204 (patch)
tree76498b837fc5f1f6ba0a5f53e1b2d85c6638da4d /tests
parent473908c9722eeedeec1777237a135f582faa78d8 (diff)
downloadmilf-rs-f66d8bcf33530c858a502bfa170f2383a8cbc204.tar.gz
milf-rs-f66d8bcf33530c858a502bfa170f2383a8cbc204.zip
Rewrite crate with serde support from ground up
This commit completely rewrites this crate from the ground up, supporting serde at the lowest levels as I believe serde support was intended to do. This is a major change from the previous versions of this crate, with a summary of changes being: * Serialization directly to TOML is now supported without going through a `Value` first. * Deserialization directly from TOML is now supported without going through a `Value`. Note that due to the TOML format some values still are buffered in intermediate memory, but overall this should be at a minimum now. * The API of `Value` was overhauled to match the API of `serde_json::Value`. The changes here were to: * Add `is_*` accessors * Add `get` and `get_mut` for one-field lookups. * Implement panicking lookups through `Index` The old `index` methods are now gone in favor of `get` and `Index` implementations. * A `Datetime` type has been added to represent a TOML datetime in a first-class fashion. Currently this type provides no accessors other than a `Display` implementation, but the idea is that this will grow support over time for decomposing the date. * Support for the `rustc-serialize` crate has been dropped, that'll stay on the 0.2 and 0.1 release trains. * This crate no longer supports the detection of unused fields, for that though you can use the `serde_ignored` crate on crates.io
Diffstat (limited to 'tests')
-rw-r--r--tests/datetime.rs58
-rw-r--r--tests/display.rs97
-rw-r--r--tests/formatting.rs21
-rw-r--r--tests/invalid-misc.rs12
-rw-r--r--tests/invalid.rs16
-rw-r--r--tests/invalid/datetime-malformed-no-z.toml1
-rw-r--r--tests/parser.rs495
-rw-r--r--tests/serde.rs496
-rw-r--r--tests/valid.rs68
9 files changed, 1205 insertions, 59 deletions
diff --git a/tests/datetime.rs b/tests/datetime.rs
new file mode 100644
index 0000000..948e863
--- /dev/null
+++ b/tests/datetime.rs
@@ -0,0 +1,58 @@
+extern crate toml;
+
+use std::str::FromStr;
+
+use toml::Value;
+
+#[test]
+fn times() {
+ fn good(s: &str) {
+ let to_parse = format!("foo = {}", s);
+ let value = Value::from_str(&to_parse).unwrap();
+ assert_eq!(value["foo"].as_datetime().unwrap().to_string(), s);
+ }
+
+ good("1997-09-09T09:09:09Z");
+ good("1997-09-09T09:09:09+09:09");
+ good("1997-09-09T09:09:09-09:09");
+ good("1997-09-09T09:09:09");
+ good("1997-09-09");
+ good("09:09:09");
+ good("1997-09-09T09:09:09.09Z");
+ good("1997-09-09T09:09:09.09+09:09");
+ good("1997-09-09T09:09:09.09-09:09");
+ good("1997-09-09T09:09:09.09");
+ good("09:09:09.09");
+}
+
+#[test]
+fn bad_times() {
+ fn bad(s: &str) {
+ let to_parse = format!("foo = {}", s);
+ assert!(Value::from_str(&to_parse).is_err());
+ }
+
+ bad("199-09-09");
+ bad("199709-09");
+ bad("1997-9-09");
+ bad("1997-09-9");
+ bad("1997-09-0909:09:09");
+ bad("1997-09-09T09:09:09.");
+ bad("T");
+ bad("T.");
+ bad("TZ");
+ bad("1997-09-09T09:09:09.09+");
+ bad("1997-09-09T09:09:09.09+09");
+ bad("1997-09-09T09:09:09.09+09:9");
+ bad("1997-09-09T09:09:09.09+0909");
+ bad("1997-09-09T09:09:09.09-");
+ bad("1997-09-09T09:09:09.09-09");
+ bad("1997-09-09T09:09:09.09-09:9");
+ bad("1997-09-09T09:09:09.09-0909");
+
+ bad("1997-00-09T09:09:09.09Z");
+ bad("1997-09-00T09:09:09.09Z");
+ bad("1997-09-09T30:09:09.09Z");
+ bad("1997-09-09T12:69:09.09Z");
+ bad("1997-09-09T12:09:69.09Z");
+}
diff --git a/tests/display.rs b/tests/display.rs
new file mode 100644
index 0000000..c38355a
--- /dev/null
+++ b/tests/display.rs
@@ -0,0 +1,97 @@
+extern crate toml;
+
+use std::collections::BTreeMap;
+
+use toml::Value::{String, Integer, Float, Boolean, Array, Table};
+
+macro_rules! map( ($($k:expr => $v:expr),*) => ({
+ let mut _m = BTreeMap::new();
+ $(_m.insert($k.to_string(), $v);)*
+ _m
+}) );
+
+#[test]
+fn simple_show() {
+ assert_eq!(String("foo".to_string()).to_string(),
+ "\"foo\"");
+ assert_eq!(Integer(10).to_string(),
+ "10");
+ assert_eq!(Float(10.0).to_string(),
+ "10.0");
+ assert_eq!(Float(2.4).to_string(),
+ "2.4");
+ assert_eq!(Boolean(true).to_string(),
+ "true");
+ assert_eq!(Array(vec![]).to_string(),
+ "[]");
+ assert_eq!(Array(vec![Integer(1), Integer(2)]).to_string(),
+ "[1, 2]");
+}
+
+#[test]
+fn table() {
+ assert_eq!(Table(map! { }).to_string(),
+ "");
+ assert_eq!(Table(map! {
+ "test" => Integer(2),
+ "test2" => Integer(3) }).to_string(),
+ "test = 2\ntest2 = 3\n");
+ assert_eq!(Table(map! {
+ "test" => Integer(2),
+ "test2" => Table(map! {
+ "test" => String("wut".to_string())
+ })
+ }).to_string(),
+ "test = 2\n\
+ \n\
+ [test2]\n\
+ test = \"wut\"\n");
+ assert_eq!(Table(map! {
+ "test" => Integer(2),
+ "test2" => Table(map! {
+ "test" => String("wut".to_string())
+ })
+ }).to_string(),
+ "test = 2\n\
+ \n\
+ [test2]\n\
+ test = \"wut\"\n");
+ assert_eq!(Table(map! {
+ "test" => Integer(2),
+ "test2" => Array(vec![Table(map! {
+ "test" => String("wut".to_string())
+ })])
+ }).to_string(),
+ "test = 2\n\
+ \n\
+ [[test2]]\n\
+ test = \"wut\"\n");
+ assert_eq!(Table(map! {
+ "foo.bar" => Integer(2),
+ "foo\"bar" => Integer(2)
+ }).to_string(),
+ "\"foo\\\"bar\" = 2\n\
+ \"foo.bar\" = 2\n");
+ assert_eq!(Table(map! {
+ "test" => Integer(2),
+ "test2" => Array(vec![Table(map! {
+ "test" => Array(vec![Integer(2)])
+ })])
+ }).to_string(),
+ "test = 2\n\
+ \n\
+ [[test2]]\n\
+ test = [2]\n");
+ let table = Table(map! {
+ "test" => Integer(2),
+ "test2" => Array(vec![Table(map! {
+ "test" => Array(vec![Array(vec![Integer(2), Integer(3)]),
+ Array(vec![String("foo".to_string()), String("bar".to_string())])])
+ })])
+ });
+ assert_eq!(table.to_string(),
+ "test = 2\n\
+ \n\
+ [[test2]]\n\
+ test = [[2, 3], [\"foo\", \"bar\"]]\n");
+}
diff --git a/tests/formatting.rs b/tests/formatting.rs
index b8f4082..10fb165 100644
--- a/tests/formatting.rs
+++ b/tests/formatting.rs
@@ -1,19 +1,22 @@
-extern crate rustc_serialize;
+extern crate serde;
+#[macro_use]
+extern crate serde_derive;
extern crate toml;
-use toml::encode_str;
-#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
+use toml::to_string;
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct User {
pub name: String,
pub surname: String,
}
-#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct Users {
pub user: Vec<User>,
}
-#[derive(Debug, Clone, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct TwoUsers {
pub user0: User,
pub user1: User,
@@ -21,7 +24,7 @@ struct TwoUsers {
#[test]
fn no_unnecessary_newlines_array() {
- assert!(!encode_str(&Users {
+ assert!(!to_string(&Users {
user: vec![
User {
name: "John".to_string(),
@@ -32,13 +35,13 @@ fn no_unnecessary_newlines_array() {
surname: "Dough".to_string(),
},
],
- })
+ }).unwrap()
.starts_with("\n"));
}
#[test]
fn no_unnecessary_newlines_table() {
- assert!(!encode_str(&TwoUsers {
+ assert!(!to_string(&TwoUsers {
user0: User {
name: "John".to_string(),
surname: "Doe".to_string(),
@@ -47,6 +50,6 @@ fn no_unnecessary_newlines_table() {
name: "Jane".to_string(),
surname: "Dough".to_string(),
},
- })
+ }).unwrap()
.starts_with("\n"));
}
diff --git a/tests/invalid-misc.rs b/tests/invalid-misc.rs
new file mode 100644
index 0000000..e8360fd
--- /dev/null
+++ b/tests/invalid-misc.rs
@@ -0,0 +1,12 @@
+extern crate toml;
+
+#[test]
+fn bad() {
+ fn bad(s: &str) {
+ assert!(s.parse::<toml::Value>().is_err());
+ }
+
+ bad("a = 01");
+ bad("a = 1__1");
+ bad("a = 1_");
+}
diff --git a/tests/invalid.rs b/tests/invalid.rs
index 63e4de8..4679684 100644
--- a/tests/invalid.rs
+++ b/tests/invalid.rs
@@ -1,17 +1,9 @@
extern crate toml;
-use toml::{Parser};
-
fn run(toml: &str) {
- let mut p = Parser::new(toml);
- let table = p.parse();
- assert!(table.is_none());
- assert!(p.errors.len() > 0);
-
- // test Parser::to_linecol with the generated error offsets
- for error in &p.errors {
- p.to_linecol(error.lo);
- p.to_linecol(error.hi);
+ println!("test if invalid:\n{}", toml);
+ if let Ok(e) = toml.parse::<toml::Value>() {
+ panic!("parsed to: {:#?}", e);
}
}
@@ -32,8 +24,6 @@ test!(datetime_malformed_no_secs,
include_str!("invalid/datetime-malformed-no-secs.toml"));
test!(datetime_malformed_no_t,
include_str!("invalid/datetime-malformed-no-t.toml"));
-test!(datetime_malformed_no_z,
- include_str!("invalid/datetime-malformed-no-z.toml"));
test!(datetime_malformed_with_milli,
include_str!("invalid/datetime-malformed-with-milli.toml"));
test!(duplicate_keys,
diff --git a/tests/invalid/datetime-malformed-no-z.toml b/tests/invalid/datetime-malformed-no-z.toml
deleted file mode 100644
index cf66b1e..0000000
--- a/tests/invalid/datetime-malformed-no-z.toml
+++ /dev/null
@@ -1 +0,0 @@
-no-z = 1987-07-05T17:45:00
diff --git a/tests/parser.rs b/tests/parser.rs
new file mode 100644
index 0000000..2db2cfb
--- /dev/null
+++ b/tests/parser.rs
@@ -0,0 +1,495 @@
+extern crate toml;
+
+use toml::Value;
+
+macro_rules! bad {
+ ($s:expr, $msg:expr) => ({
+ match $s.parse::<Value>() {
+ Ok(s) => panic!("successfully parsed as {}", s),
+ Err(e) => {
+ let e = e.to_string();
+ assert!(e.contains($msg), "error: {}", e);
+ }
+ }
+ })
+}
+
+#[test]
+fn crlf() {
+ "\
+[project]\r\n\
+\r\n\
+name = \"splay\"\r\n\
+version = \"0.1.0\"\r\n\
+authors = [\"alex@crichton.co\"]\r\n\
+\r\n\
+[[lib]]\r\n\
+\r\n\
+path = \"lib.rs\"\r\n\
+name = \"splay\"\r\n\
+description = \"\"\"\
+A Rust implementation of a TAR file reader and writer. This library does not\r\n\
+currently handle compression, but it is abstract over all I/O readers and\r\n\
+writers. Additionally, great lengths are taken to ensure that the entire\r\n\
+contents are never required to be entirely resident in memory all at once.\r\n\
+\"\"\"\
+".parse::<Value>().unwrap();
+}
+
+#[test]
+fn fun_with_strings() {
+ let table = r#"
+bar = "\U00000000"
+key1 = "One\nTwo"
+key2 = """One\nTwo"""
+key3 = """
+One
+Two"""
+
+key4 = "The quick brown fox jumps over the lazy dog."
+key5 = """
+The quick brown \
+
+
+fox jumps over \
+the lazy dog."""
+key6 = """\
+ The quick brown \
+ fox jumps over \
+ the lazy dog.\
+ """
+# What you see is what you get.
+winpath = 'C:\Users\nodejs\templates'
+winpath2 = '\\ServerX\admin$\system32\'
+quoted = 'Tom "Dubs" Preston-Werner'
+regex = '<\i\c*\s*>'
+
+regex2 = '''I [dw]on't need \d{2} apples'''
+lines = '''
+The first newline is
+trimmed in raw strings.
+All other whitespace
+is preserved.
+'''
+"#.parse::<Value>().unwrap();
+ assert_eq!(table["bar"].as_str(), Some("\0"));
+ assert_eq!(table["key1"].as_str(), Some("One\nTwo"));
+ assert_eq!(table["key2"].as_str(), Some("One\nTwo"));
+ assert_eq!(table["key3"].as_str(), Some("One\nTwo"));
+
+ let msg = "The quick brown fox jumps over the lazy dog.";
+ assert_eq!(table["key4"].as_str(), Some(msg));
+ assert_eq!(table["key5"].as_str(), Some(msg));
+ assert_eq!(table["key6"].as_str(), Some(msg));
+
+ assert_eq!(table["winpath"].as_str(), Some(r"C:\Users\nodejs\templates"));
+ assert_eq!(table["winpath2"].as_str(), Some(r"\\ServerX\admin$\system32\"));
+ assert_eq!(table["quoted"].as_str(), Some(r#"Tom "Dubs" Preston-Werner"#));
+ assert_eq!(table["regex"].as_str(), Some(r"<\i\c*\s*>"));
+ assert_eq!(table["regex2"].as_str(), Some(r"I [dw]on't need \d{2} apples"));
+ assert_eq!(table["lines"].as_str(),
+ Some("The first newline is\n\
+ trimmed in raw strings.\n\
+ All other whitespace\n\
+ is preserved.\n"));
+}
+
+#[test]
+fn tables_in_arrays() {
+ let table = r#"
+[[foo]]
+#…
+[foo.bar]
+#…
+
+[[foo]] # ...
+#…
+[foo.bar]
+#...
+"#.parse::<Value>().unwrap();
+ table["foo"][0]["bar"].as_table().unwrap();
+ table["foo"][1]["bar"].as_table().unwrap();
+}
+
+#[test]
+fn empty_table() {
+ let table = r#"
+[foo]"#.parse::<Value>().unwrap();
+ table["foo"].as_table().unwrap();
+}
+
+#[test]
+fn fruit() {
+ let table = r#"
+[[fruit]]
+name = "apple"
+
+[fruit.physical]
+color = "red"
+shape = "round"
+
+[[fruit.variety]]
+name = "red delicious"
+
+[[fruit.variety]]
+name = "granny smith"
+
+[[fruit]]
+name = "banana"
+
+[[fruit.variety]]
+name = "plantain"
+"#.parse::<Value>().unwrap();
+ assert_eq!(table["fruit"][0]["name"].as_str(), Some("apple"));
+ assert_eq!(table["fruit"][0]["physical"]["color"].as_str(), Some("red"));
+ assert_eq!(table["fruit"][0]["physical"]["shape"].as_str(), Some("round"));
+ assert_eq!(table["fruit"][0]["variety"][0]["name"].as_str(), Some("red delicious"));
+ assert_eq!(table["fruit"][0]["variety"][1]["name"].as_str(), Some("granny smith"));
+ assert_eq!(table["fruit"][1]["name"].as_str(), Some("banana"));
+ assert_eq!(table["fruit"][1]["variety"][0]["name"].as_str(), Some("plantain"));
+}
+
+#[test]
+fn stray_cr() {
+ "\r".parse::<Value>().unwrap_err();
+ "a = [ \r ]".parse::<Value>().unwrap_err();
+ "a = \"\"\"\r\"\"\"".parse::<Value>().unwrap_err();
+ "a = \"\"\"\\ \r \"\"\"".parse::<Value>().unwrap_err();
+ "a = '''\r'''".parse::<Value>().unwrap_err();
+ "a = '\r'".parse::<Value>().unwrap_err();
+ "a = \"\r\"".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn blank_literal_string() {
+ let table = "foo = ''".parse::<Value>().unwrap();
+ assert_eq!(table["foo"].as_str(), Some(""));
+}
+
+#[test]
+fn many_blank() {
+ let table = "foo = \"\"\"\n\n\n\"\"\"".parse::<Value>().unwrap();
+ assert_eq!(table["foo"].as_str(), Some("\n\n"));
+}
+
+#[test]
+fn literal_eats_crlf() {
+ let table = "
+ foo = \"\"\"\\\r\n\"\"\"
+ bar = \"\"\"\\\r\n \r\n \r\n a\"\"\"
+ ".parse::<Value>().unwrap();
+ assert_eq!(table["foo"].as_str(), Some(""));
+ assert_eq!(table["bar"].as_str(), Some("a"));
+}
+
+#[test]
+fn string_no_newline() {
+ "a = \"\n\"".parse::<Value>().unwrap_err();
+ "a = '\n'".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn bad_leading_zeros() {
+ "a = 00".parse::<Value>().unwrap_err();
+ "a = -00".parse::<Value>().unwrap_err();
+ "a = +00".parse::<Value>().unwrap_err();
+ "a = 00.0".parse::<Value>().unwrap_err();
+ "a = -00.0".parse::<Value>().unwrap_err();
+ "a = +00.0".parse::<Value>().unwrap_err();
+ "a = 9223372036854775808".parse::<Value>().unwrap_err();
+ "a = -9223372036854775809".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn bad_floats() {
+ "a = 0.".parse::<Value>().unwrap_err();
+ "a = 0.e".parse::<Value>().unwrap_err();
+ "a = 0.E".parse::<Value>().unwrap_err();
+ "a = 0.0E".parse::<Value>().unwrap_err();
+ "a = 0.0e".parse::<Value>().unwrap_err();
+ "a = 0.0e-".parse::<Value>().unwrap_err();
+ "a = 0.0e+".parse::<Value>().unwrap_err();
+ "a = 0.0e+00".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn floats() {
+ macro_rules! t {
+ ($actual:expr, $expected:expr) => ({
+ let f = format!("foo = {}", $actual);
+ println!("{}", f);
+ let a = f.parse::<Value>().unwrap();
+ assert_eq!(a["foo"].as_float().unwrap(), $expected);
+ })
+ }
+
+ t!("1.0", 1.0);
+ t!("1.0e0", 1.0);
+ t!("1.0e+0", 1.0);
+ t!("1.0e-0", 1.0);
+ t!("1.001e-0", 1.001);
+ t!("2e10", 2e10);
+ t!("2e+10", 2e10);
+ t!("2e-10", 2e-10);
+ t!("2_0.0", 20.0);
+ t!("2_0.0_0e1_0", 20.0e10);
+ t!("2_0.1_0e1_0", 20.1e10);
+}
+
+#[test]
+fn bare_key_names() {
+ let a = "
+ foo = 3
+ foo_3 = 3
+ foo_-2--3--r23f--4-f2-4 = 3
+ _ = 3
+ - = 3
+ 8 = 8
+ \"a\" = 3
+ \"!\" = 3
+ \"a^b\" = 3
+ \"\\\"\" = 3
+ \"character encoding\" = \"value\"
+ 'ʎǝʞ' = \"value\"
+ ".parse::<Value>().unwrap();
+ &a["foo"];
+ &a["-"];
+ &a["_"];
+ &a["8"];
+ &a["foo_3"];
+ &a["foo_-2--3--r23f--4-f2-4"];
+ &a["a"];
+ &a["!"];
+ &a["\""];
+ &a["character encoding"];
+ &a["ʎǝʞ"];
+}
+
+#[test]
+fn bad_keys() {
+ "key\n=3".parse::<Value>().unwrap_err();
+ "key=\n3".parse::<Value>().unwrap_err();
+ "key|=3".parse::<Value>().unwrap_err();
+ "\"\"=3".parse::<Value>().unwrap_err();
+ "=3".parse::<Value>().unwrap_err();
+ "\"\"|=3".parse::<Value>().unwrap_err();
+ "\"\n\"|=3".parse::<Value>().unwrap_err();
+ "\"\r\"|=3".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn bad_table_names() {
+ "[]".parse::<Value>().unwrap_err();
+ "[.]".parse::<Value>().unwrap_err();
+ "[\"\".\"\"]".parse::<Value>().unwrap_err();
+ "[a.]".parse::<Value>().unwrap_err();
+ "[\"\"]".parse::<Value>().unwrap_err();
+ "[!]".parse::<Value>().unwrap_err();
+ "[\"\n\"]".parse::<Value>().unwrap_err();
+ "[a.b]\n[a.\"b\"]".parse::<Value>().unwrap_err();
+ "[']".parse::<Value>().unwrap_err();
+ "[''']".parse::<Value>().unwrap_err();
+ "['''''']".parse::<Value>().unwrap_err();
+ "['\n']".parse::<Value>().unwrap_err();
+ "['\r\n']".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn table_names() {
+ let a = "
+ [a.\"b\"]
+ [\"f f\"]
+ [\"f.f\"]
+ [\"\\\"\"]
+ ['a.a']
+ ['\"\"']
+ ".parse::<Value>().unwrap();
+ println!("{:?}", a);
+ &a["a"]["b"];
+ &a["f f"];
+ &a["f.f"];
+ &a["\""];
+ &a["\"\""];
+}
+
+#[test]
+fn invalid_bare_numeral() {
+ "4".parse::<Value>().unwrap_err();
+}
+
+#[test]
+fn inline_tables() {
+ "a = {}".parse::<Value>().unwrap();
+ "a = {b=1}".parse::<Value>().unwrap();
+ "a = { b = 1 }".parse::<Value>().unwrap();
+ "a = {a=1,b=2}".parse::<Value>().unwrap();
+ "a = {a=1,b=2,c={}}".parse::<Value>().unwrap();
+ "a = {a=1,}".parse::<Value>().unwrap_err();
+ "a = {,}".parse::<Value>().unwrap_err();
+ "a = {a=1,a=1}".parse::<Value>().unwrap_err();
+ "a = {\n}".parse::<Value>().unwrap_err();
+ "a = {".parse::<Value>().unwrap_err();
+ "a = {a=[\n]}".parse::<Value>().unwrap();
+ "a = {\"a\"=[\n]}".parse::<Value>().unwrap();
+ "a = [\n{},\n{},\n]".parse::<Value>().unwrap();
+}
+
+#[test]
+fn number_underscores() {
+ macro_rules! t {
+ ($actual:expr, $expected:expr) => ({
+ let f = format!("foo = {}", $actual);
+ let table = f.parse::<Value>().unwrap();
+ assert_eq!(table["foo"].as_integer().unwrap(), $expected);
+ })
+ }
+
+ t!("1_0", 10);
+ t!("1_0_0", 100);
+ t!("1_000", 1000);
+ t!("+1_000", 1000);
+ t!("-1_000", -1000);
+}
+
+#[test]
+fn bad_underscores() {
+ bad!("foo = 0_", "invalid number");
+ bad!("foo = 0__0", "invalid number");
+ bad!("foo = __0", "invalid number");
+ bad!("foo = 1_0_", "invalid number");
+}
+
+#[test]
+fn bad_unicode_codepoint() {
+ bad!("foo = \"\\uD800\"", "invalid escape value");
+}
+
+#[test]
+fn bad_strings() {
+ bad!("foo = \"\\uxx\"", "invalid hex escape");
+ bad!("foo = \"\\u\"", "invalid hex escape");
+ bad!("foo = \"\\", "unterminated");
+ bad!("foo = '", "unterminated");
+}
+
+#[test]
+fn empty_string() {
+ assert_eq!("foo = \"\"".parse::<Value>()
+ .unwrap()["foo"]
+ .as_str()
+ .unwrap(),
+ "");
+}
+
+#[test]
+fn booleans() {
+ let table = "foo = true".parse::<Value>().unwrap();
+ assert_eq!(table["foo"].as_bool(), Some(true));
+
+ let table = "foo = false".parse::<Value>().unwrap();
+ assert_eq!(table["foo"].as_bool(), Some(false));
+
+ assert!("foo = true2".parse::<Value>().is_err());
+ assert!("foo = false2".parse::<Value>().is_err());
+ assert!("foo = t1".parse::<Value>().is_err());
+ assert!("foo = f2".parse::<Value>().is_err());
+}
+
+#[test]
+fn bad_nesting() {
+ bad!("
+ a = [2]
+ [[a]]
+ b = 5
+ ", "duplicate key: `a`");
+ bad!("
+ a = 1
+ [a.b]
+ ", "duplicate key: `a`");
+ bad!("
+ a = []
+ [a.b]
+ ", "duplicate key: `a`");
+ bad!("
+ a = []
+ [[a.b]]
+ ", "duplicate key: `a`");
+ bad!("
+ [a]
+ b = { c = 2, d = {} }
+ [a.b]
+ c = 2
+ ", "duplicate key: `b`");
+}
+
+#[test]
+fn bad_table_redefine() {
+ bad!("
+ [a]
+ foo=\"bar\"
+ [a.b]
+ foo=\"bar\"
+ [a]
+ ", "redefinition of table `a`");
+ bad!("
+ [a]
+ foo=\"bar\"
+ b = { foo = \"bar\" }
+ [a]
+ ", "redefinition of table `a`");
+ bad!("
+ [a]
+ b = {}
+ [a.b]
+ ", "duplicate key: `b`");
+
+ bad!("
+ [a]
+ b = {}
+ [a]
+ ", "redefinition of table `a`");
+}
+
+#[test]
+fn datetimes() {
+ macro_rules! t {
+ ($actual:expr) => ({
+ let f = format!("foo = {}", $actual);
+ let toml = f.parse::<Value>().expect(&format!("failed: {}", f));
+ assert_eq!(toml["foo"].as_datetime().unwrap().to_string(), $actual);
+ })
+ }
+
+ t!("2016-09-09T09:09:09Z");
+ t!("2016-09-09T09:09:09.1Z");
+ t!("2016-09-09T09:09:09.2+10:00");
+ t!("2016-09-09T09:09:09.0123456789-02:00");
+ bad!("foo = 2016-09-09T09:09:09.Z", "failed to parse date");
+ bad!("foo = 2016-9-09T09:09:09Z", "failed to parse date");
+ bad!("foo = 2016-09-09T09:09:09+2:00", "failed to parse date");
+ bad!("foo = 2016-09-09T09:09:09-2:00", "failed to parse date");
+ bad!("foo = 2016-09-09T09:09:09Z-2:00", "failed to parse date");
+}
+
+#[test]
+fn require_newline_after_value() {
+ bad!("0=0r=false", "invalid number at line 1");
+ bad!(r#"
+0=""o=""m=""r=""00="0"q="""0"""e="""0"""
+"#, "expected newline");
+ bad!(r#"
+[[0000l0]]
+0="0"[[0000l0]]
+0="0"[[0000l0]]
+0="0"l="0"
+"#, "expected newline");
+ bad!(r#"
+0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
+"#, "expected newline");
+ bad!(r#"
+0=0r0=0r=false
+"#, "invalid number at line 2");
+ bad!(r#"
+0=0r0=0r=falsefal=false
+"#, "invalid number at line 2");
+}
diff --git a/tests/serde.rs b/tests/serde.rs
new file mode 100644
index 0000000..e4d88fc
--- /dev/null
+++ b/tests/serde.rs
@@ -0,0 +1,496 @@
+extern crate serde;
+extern crate toml;
+#[macro_use]
+extern crate serde_derive;
+
+use std::collections::{BTreeMap, HashSet};
+use serde::{Deserialize, Deserializer};
+
+use toml::Value;
+use toml::Value::{Table, Integer, Array, Float};
+
+macro_rules! t {
+ ($e:expr) => (match $e {
+ Ok(t) => t,
+ Err(e) => panic!("{} failed with {}", stringify!($e), e),
+ })
+}
+
+macro_rules! equivalent {
+ ($literal:expr, $toml:expr,) => ({
+ let toml = $toml;
+ let literal = $literal;
+
+ // In/out of Value is equivalent
+ println!("try_from");
+ assert_eq!(t!(Value::try_from(literal.clone())), toml);
+ println!("try_into");
+ assert_eq!(literal, t!(toml.clone().try_into()));
+
+ // Through a string equivalent
+ println!("to_string(literal)");
+ assert_eq!(t!(toml::to_string(&literal)), toml.to_string());
+ println!("to_string(toml)");
+ assert_eq!(t!(toml::to_string(&toml)), toml.to_string());
+ println!("literal, from_str(toml)");
+ assert_eq!(literal, t!(toml::from_str(&toml.to_string())));
+ println!("toml, from_str(toml)");
+ assert_eq!(toml, t!(toml::from_str(&toml.to_string())));
+ })
+}
+
+macro_rules! error {
+ ($ty:ty, $toml:expr, $error:expr) => ({
+ println!("attempting parsing");
+ match toml::from_str::<$ty>(&$toml.to_string()) {
+ Ok(_) => panic!("successful"),
+ Err(e) => {
+ assert!(e.to_string().contains($error),
+ "bad error: {}", e);
+ }
+ }
+
+ println!("attempting toml decoding");
+ match $toml.try_into::<$ty>() {
+ Ok(_) => panic!("successful"),
+ Err(e) => {
+ assert!(e.to_string().contains($error),
+ "bad error: {}", e);
+ }
+ }
+ })
+}
+
+macro_rules! decode( ($t:expr) => ({
+ t!($t.try_into())
+}) );
+
+macro_rules! map( ($($k:ident: $v:expr),*) => ({
+ let mut _m = BTreeMap::new();
+ $(_m.insert(stringify!($k).to_string(), $v);)*
+ _m
+}) );
+
+#[test]
+fn smoke() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: isize }
+
+ equivalent!(
+ Foo { a: 2 },
+ Table(map! { a: Integer(2) }),
+ );
+}
+
+#[test]
+fn smoke_hyphen() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ a_b: isize,
+ }
+
+ equivalent! {
+ Foo { a_b: 2 },
+ Table(map! { a_b: Integer(2) }),
+ }
+
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo2 {
+ #[serde(rename = "a-b")]
+ a_b: isize,
+ }
+
+ let mut m = BTreeMap::new();
+ m.insert("a-b".to_string(), Integer(2));
+ equivalent! {
+ Foo2 { a_b: 2 },
+ Table(m),
+ }
+}
+
+#[test]
+fn nested() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: isize, b: Bar }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Bar { a: String }
+
+ equivalent! {
+ Foo { a: 2, b: Bar { a: "test".to_string() } },
+ Table(map! {
+ a: Integer(2),
+ b: Table(map! {
+ a: Value::String("test".to_string())
+ })
+ }),
+ }
+}
+
+#[test]
+fn application_decode_error() {
+ #[derive(PartialEq, Debug)]
+ struct Range10(usize);
+ impl Deserialize for Range10 {
+ fn deserialize<D: Deserializer>(d: D) -> Result<Range10, D::Error> {
+ let x: usize = try!(Deserialize::deserialize(d));
+ if x > 10 {
+ Err(serde::de::Error::custom("more than 10"))
+ } else {
+ Ok(Range10(x))
+ }
+ }
+ }
+ let d_good = Integer(5);
+ let d_bad1 = Value::String("not an isize".to_string());
+ let d_bad2 = Integer(11);
+
+ assert_eq!(Range10(5), d_good.try_into().unwrap());
+
+ let err1: Result<Range10, _> = d_bad1.try_into();
+ assert!(err1.is_err());
+ let err2: Result<Range10, _> = d_bad2.try_into();
+ assert!(err2.is_err());
+}
+
+#[test]
+fn array() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: Vec<isize> }
+
+ equivalent! {
+ Foo { a: vec![1, 2, 3, 4] },
+ Table(map! {
+ a: Array(vec![
+ Integer(1),
+ Integer(2),
+ Integer(3),
+ Integer(4)
+ ])
+ }),
+ };
+}
+
+#[test]
+fn inner_structs_with_options() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ a: Option<Box<Foo>>,
+ b: Bar,
+ }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Bar {
+ a: String,
+ b: f64,
+ }
+
+ equivalent! {
+ Foo {
+ a: Some(Box::new(Foo {
+ a: None,
+ b: Bar { a: "foo".to_string(), b: 4.5 },
+ })),
+ b: Bar { a: "bar".to_string(), b: 1.0 },
+ },
+ Table(map! {
+ a: Table(map! {
+ b: Table(map! {
+ a: Value::String("foo".to_string()),
+ b: Float(4.5)
+ })
+ }),
+ b: Table(map! {
+ a: Value::String("bar".to_string()),
+ b: Float(1.0)
+ })
+ }),
+ }
+}
+
+#[test]
+fn hashmap() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo {
+ set: HashSet<char>,
+ map: BTreeMap<String, isize>,
+ }
+
+ equivalent! {
+ Foo {
+ map: {
+ let mut m = BTreeMap::new();
+ m.insert("foo".to_string(), 10);
+ m.insert("bar".to_string(), 4);
+ m
+ },
+ set: {
+ let mut s = HashSet::new();
+ s.insert('a');
+ s
+ },
+ },
+ Table(map! {
+ map: Table(map! {
+ foo: Integer(10),
+ bar: Integer(4)
+ }),
+ set: Array(vec![Value::String("a".to_string())])
+ }),
+ }
+}
+
+#[test]
+fn table_array() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: Vec<Bar>, }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Bar { a: isize }
+
+ equivalent! {
+ Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] },
+ Table(map! {
+ a: Array(vec![
+ Table(map!{ a: Integer(1) }),
+ Table(map!{ a: Integer(2) }),
+ ])
+ }),
+ }
+}
+
+#[test]
+fn type_errors() {
+ #[derive(Deserialize)]
+ #[allow(dead_code)]
+ struct Foo { bar: isize }
+
+ error! {
+ Foo,
+ Table(map! {
+ bar: Value::String("a".to_string())
+ }),
+ "invalid type: string \"a\", expected isize for key `bar`"
+ }
+
+ #[derive(Deserialize)]
+ #[allow(dead_code)]
+ struct Bar { foo: Foo }
+
+ error! {
+ Bar,
+ Table(map! {
+ foo: Table(map! {
+ bar: Value::String("a".to_string())
+ })
+ }),
+ "invalid type: string \"a\", expected isize for key `foo.bar`"
+ }
+}
+
+#[test]
+fn missing_errors() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug)]
+ struct Foo { bar: isize }
+
+ error! {
+ Foo,
+ Table(map! { }),
+ "missing field `bar`"
+ }
+}
+
+#[test]
+fn parse_enum() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: E }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ #[serde(untagged)]
+ enum E {
+ Bar(isize),
+ Baz(String),
+ Last(Foo2),
+ }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo2 {
+ test: String,
+ }
+
+ equivalent! {
+ Foo { a: E::Bar(10) },
+ Table(map! { a: Integer(10) }),
+ }
+
+ equivalent! {
+ Foo { a: E::Baz("foo".to_string()) },
+ Table(map! { a: Value::String("foo".to_string()) }),
+ }
+
+ equivalent! {
+ Foo { a: E::Last(Foo2 { test: "test".to_string() }) },
+ Table(map! { a: Table(map! { test: Value::String("test".to_string()) }) }),
+ }
+}
+
+// #[test]
+// fn unused_fields() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: isize }
+//
+// let v = Foo { a: 2 };
+// let mut d = Decoder::new(Table(map! {
+// a, Integer(2),
+// b, Integer(5)
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, Some(Table(map! {
+// b, Integer(5)
+// })));
+// }
+//
+// #[test]
+// fn unused_fields2() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: Bar }
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Bar { a: isize }
+//
+// let v = Foo { a: Bar { a: 2 } };
+// let mut d = Decoder::new(Table(map! {
+// a, Table(map! {
+// a, Integer(2),
+// b, Integer(5)
+// })
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, Some(Table(map! {
+// a, Table(map! {
+// b, Integer(5)
+// })
+// })));
+// }
+//
+// #[test]
+// fn unused_fields3() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: Bar }
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Bar { a: isize }
+//
+// let v = Foo { a: Bar { a: 2 } };
+// let mut d = Decoder::new(Table(map! {
+// a, Table(map! {
+// a, Integer(2)
+// })
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields4() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: BTreeMap<String, String> }
+//
+// let v = Foo { a: map! { a, "foo".to_string() } };
+// let mut d = Decoder::new(Table(map! {
+// a, Table(map! {
+// a, Value::String("foo".to_string())
+// })
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields5() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: Vec<String> }
+//
+// let v = Foo { a: vec!["a".to_string()] };
+// let mut d = Decoder::new(Table(map! {
+// a, Array(vec![Value::String("a".to_string())])
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields6() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: Option<Vec<String>> }
+//
+// let v = Foo { a: Some(vec![]) };
+// let mut d = Decoder::new(Table(map! {
+// a, Array(vec![])
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields7() {
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Foo { a: Vec<Bar> }
+// #[derive(Serialize, Deserialize, PartialEq, Debug)]
+// struct Bar { a: isize }
+//
+// let v = Foo { a: vec![Bar { a: 1 }] };
+// let mut d = Decoder::new(Table(map! {
+// a, Array(vec![Table(map! {
+// a, Integer(1),
+// b, Integer(2)
+// })])
+// }));
+// assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+// assert_eq!(d.toml, Some(Table(map! {
+// a, Array(vec![Table(map! {
+// b, Integer(2)
+// })])
+// })));
+// }
+
+#[test]
+fn empty_arrays() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: Vec<Bar> }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Bar;
+
+ equivalent! {
+ Foo { a: vec![] },
+ Table(map! {a: Array(Vec::new())}),
+ }
+}
+
+#[test]
+fn empty_arrays2() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Foo { a: Option<Vec<Bar>> }
+ #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+ struct Bar;
+
+ equivalent! {
+ Foo { a: None },
+ Table(map! {}),
+ }
+
+ equivalent!{
+ Foo { a: Some(vec![]) },
+ Table(map! { a: Array(vec![]) }),
+ }
+}
+
+#[test]
+fn extra_keys() {
+ #[derive(Serialize, Deserialize)]
+ struct Foo { a: isize }
+
+ let toml = Table(map! { a: Integer(2), b: Integer(2) });
+ assert!(toml.clone().try_into::<Foo>().is_ok());
+ assert!(toml::from_str::<Foo>(&toml.to_string()).is_ok());
+}
diff --git a/tests/valid.rs b/tests/valid.rs
index 09589c9..4229f1c 100644
--- a/tests/valid.rs
+++ b/tests/valid.rs
@@ -1,65 +1,61 @@
-extern crate rustc_serialize;
extern crate toml;
+extern crate serde_json;
-use std::collections::BTreeMap;
-use rustc_serialize::json::Json;
+use toml::Value as Toml;
+use serde_json::Value as Json;
-use toml::{Parser, Value};
-use toml::Value::{Table, Integer, Float, Boolean, Datetime, Array};
-
-fn to_json(toml: Value) -> Json {
+fn to_json(toml: toml::Value) -> Json {
fn doit(s: &str, json: Json) -> Json {
- let mut map = BTreeMap::new();
- map.insert(format!("{}", "type"), Json::String(format!("{}", s)));
- map.insert(format!("{}", "value"), json);
+ let mut map = serde_json::Map::new();
+ map.insert("type".to_string(), Json::String(s.to_string()));
+ map.insert("value".to_string(), json);
Json::Object(map)
}
+
match toml {
- Value::String(s) => doit("string", Json::String(s)),
- Integer(i) => doit("integer", Json::String(format!("{}", i))),
- Float(f) => doit("float", Json::String({
+ Toml::String(s) => doit("string", Json::String(s)),
+ Toml::Integer(i) => doit("integer", Json::String(i.to_string())),
+ Toml::Float(f) => doit("float", Json::String({
let s = format!("{:.15}", f);
let s = format!("{}", s.trim_right_matches('0'));
if s.ends_with(".") {format!("{}0", s)} else {s}
})),
- Boolean(b) => doit("bool", Json::String(format!("{}", b))),
- Datetime(s) => doit("datetime", Json::String(s)),
- Array(arr) => {
+ Toml::Boolean(b) => doit("bool", Json::String(format!("{}", b))),
+ Toml::Datetime(s) => doit("datetime", Json::String(s.to_string())),
+ Toml::Array(arr) => {
let is_table = match arr.first() {
- Some(&Table(..)) => true,
+ Some(&Toml::Table(..)) => true,
_ => false,
};
let json = Json::Array(arr.into_iter().map(to_json).collect());
if is_table {json} else {doit("array", json)}
}
- Table(table) => Json::Object(table.into_iter().map(|(k, v)| {
- (k, to_json(v))
- }).collect()),
+ Toml::Table(table) => {
+ let mut map = serde_json::Map::new();
+ for (k, v) in table {
+ map.insert(k, to_json(v));
+ }
+ Json::Object(map)
+ }
}
}
fn run(toml: &str, json: &str) {
- let mut p = Parser::new(toml);
- let table = p.parse();
- assert!(p.errors.len() == 0, "had_errors: {:?}",
- p.errors.iter().map(|e| {
- (e.desc.clone(), &toml[e.lo - 5..e.hi + 5])
- }).collect::<Vec<(String, &str)>>());
- assert!(table.is_some());
- let toml = Table(table.unwrap());
- let toml_string = format!("{}", toml);
+ println!("parsing:\n{}", toml);
+ let toml: Toml = toml.parse().unwrap();
+ let json: Json = json.parse().unwrap();
- let json = Json::from_str(json).unwrap();
+ // Assert toml == json
let toml_json = to_json(toml.clone());
assert!(json == toml_json,
"expected\n{}\ngot\n{}\n",
- json.pretty(),
- toml_json.pretty());
+ serde_json::to_string_pretty(&json).unwrap(),
+ serde_json::to_string_pretty(&toml_json).unwrap());
- let table2 = Parser::new(&toml_string).parse().unwrap();
- // floats are a little lossy
- if table2.values().any(|v| v.as_float().is_some()) { return }
- assert_eq!(toml, Table(table2));
+ // Assert round trip
+ println!("round trip parse: {}", toml);
+ let toml2 = toml.to_string().parse().unwrap();
+ assert_eq!(toml, toml2);
}
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (