461 lines
9.5 KiB
Rust
461 lines
9.5 KiB
Rust
use serde::{de, Deserialize};
|
|
use std::fmt;
|
|
|
|
macro_rules! bad {
|
|
($toml:expr, $ty:ty, $msg:expr) => {
|
|
match toml::from_str::<$ty>($toml) {
|
|
Ok(s) => panic!("parsed to: {:#?}", s),
|
|
Err(e) => snapbox::assert_eq($msg, e.to_string()),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
|
struct Parent<T> {
|
|
p_a: T,
|
|
p_b: Vec<Child<T>>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct Child<T> {
|
|
c_a: T,
|
|
c_b: T,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
enum CasedString {
|
|
Lowercase(String),
|
|
Uppercase(String),
|
|
}
|
|
|
|
impl<'de> de::Deserialize<'de> for CasedString {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: de::Deserializer<'de>,
|
|
{
|
|
struct CasedStringVisitor;
|
|
|
|
impl<'de> de::Visitor<'de> for CasedStringVisitor {
|
|
type Value = CasedString;
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
formatter.write_str("a string")
|
|
}
|
|
|
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
|
where
|
|
E: de::Error,
|
|
{
|
|
if s.is_empty() {
|
|
Err(de::Error::invalid_length(0, &"a non-empty string"))
|
|
} else if s.chars().all(|x| x.is_ascii_lowercase()) {
|
|
Ok(CasedString::Lowercase(s.to_string()))
|
|
} else if s.chars().all(|x| x.is_ascii_uppercase()) {
|
|
Ok(CasedString::Uppercase(s.to_string()))
|
|
} else {
|
|
Err(de::Error::invalid_value(
|
|
de::Unexpected::Str(s),
|
|
&"all lowercase or all uppercase",
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
deserializer.deserialize_any(CasedStringVisitor)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn custom_errors() {
|
|
toml::from_str::<Parent<CasedString>>(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [{c_a = 'a', c_b = 'c'}]
|
|
",
|
|
)
|
|
.unwrap();
|
|
|
|
// Custom error at p_b value.
|
|
bad!(
|
|
"
|
|
p_a = ''
|
|
# ^
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 2, column 19
|
|
|
|
|
2 | p_a = ''
|
|
| ^^
|
|
invalid length 0, expected a non-empty string
|
|
"
|
|
);
|
|
|
|
// Missing field in table.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
# ^
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 1, column 1
|
|
|
|
|
1 |
|
|
| ^
|
|
missing field `p_b`
|
|
"
|
|
);
|
|
|
|
// Invalid type in p_b.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = 1
|
|
# ^
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 3, column 19
|
|
|
|
|
3 | p_b = 1
|
|
| ^
|
|
invalid type: integer `1`, expected a sequence
|
|
"
|
|
);
|
|
|
|
// Sub-table in Vec is missing a field.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = 'a'}
|
|
# ^
|
|
]
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 4, column 17
|
|
|
|
|
4 | {c_a = 'a'}
|
|
| ^^^^^^^^^^^
|
|
missing field `c_b`
|
|
"
|
|
);
|
|
|
|
// Sub-table in Vec has a field with a bad value.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = 'a', c_b = '*'}
|
|
# ^
|
|
]
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 4, column 35
|
|
|
|
|
4 | {c_a = 'a', c_b = '*'}
|
|
| ^^^
|
|
invalid value: string \"*\", expected all lowercase or all uppercase
|
|
"
|
|
);
|
|
|
|
// Sub-table in Vec is missing a field.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = 'a', c_b = 'b'},
|
|
{c_a = 'aa'}
|
|
# ^
|
|
]
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 5, column 17
|
|
|
|
|
5 | {c_a = 'aa'}
|
|
| ^^^^^^^^^^^^
|
|
missing field `c_b`
|
|
"
|
|
);
|
|
|
|
// Sub-table in the middle of a Vec is missing a field.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = 'a', c_b = 'b'},
|
|
{c_a = 'aa'},
|
|
# ^
|
|
{c_a = 'aaa', c_b = 'bbb'},
|
|
]
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 5, column 17
|
|
|
|
|
5 | {c_a = 'aa'},
|
|
| ^^^^^^^^^^^^
|
|
missing field `c_b`
|
|
"
|
|
);
|
|
|
|
// Sub-table in the middle of a Vec has a field with a bad value.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = 'a', c_b = 'b'},
|
|
{c_a = 'aa', c_b = 1},
|
|
# ^
|
|
{c_a = 'aaa', c_b = 'bbb'},
|
|
]
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 5, column 36
|
|
|
|
|
5 | {c_a = 'aa', c_b = 1},
|
|
| ^
|
|
invalid type: integer `1`, expected a string
|
|
"
|
|
);
|
|
|
|
// Sub-table in the middle of a Vec has an extra field.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = 'a', c_b = 'b'},
|
|
{c_a = 'aa', c_b = 'bb', c_d = 'd'},
|
|
# ^
|
|
{c_a = 'aaa', c_b = 'bbb'},
|
|
{c_a = 'aaaa', c_b = 'bbbb'},
|
|
]
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 5, column 42
|
|
|
|
|
5 | {c_a = 'aa', c_b = 'bb', c_d = 'd'},
|
|
| ^^^
|
|
unknown field `c_d`, expected `c_a` or `c_b`
|
|
"
|
|
);
|
|
|
|
// Sub-table in the middle of a Vec is missing a field.
|
|
// FIXME: This location is pretty off.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
[[p_b]]
|
|
c_a = 'a'
|
|
c_b = 'b'
|
|
[[p_b]]
|
|
c_a = 'aa'
|
|
# c_b = 'bb' # <- missing field
|
|
[[p_b]]
|
|
c_a = 'aaa'
|
|
c_b = 'bbb'
|
|
[[p_b]]
|
|
# ^
|
|
c_a = 'aaaa'
|
|
c_b = 'bbbb'
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 6, column 13
|
|
|
|
|
6 | [[p_b]]
|
|
| ^^^^^^^^^^^^^^^^^^^
|
|
missing field `c_b`
|
|
"
|
|
);
|
|
|
|
// Sub-table in the middle of a Vec has a field with a bad value.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
[[p_b]]
|
|
c_a = 'a'
|
|
c_b = 'b'
|
|
[[p_b]]
|
|
c_a = 'aa'
|
|
c_b = '*'
|
|
# ^
|
|
[[p_b]]
|
|
c_a = 'aaa'
|
|
c_b = 'bbb'
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 8, column 19
|
|
|
|
|
8 | c_b = '*'
|
|
| ^^^
|
|
invalid value: string \"*\", expected all lowercase or all uppercase
|
|
"
|
|
);
|
|
|
|
// Sub-table in the middle of a Vec has an extra field.
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
[[p_b]]
|
|
c_a = 'a'
|
|
c_b = 'b'
|
|
[[p_b]]
|
|
c_a = 'aa'
|
|
c_d = 'dd' # unknown field
|
|
# ^
|
|
[[p_b]]
|
|
c_a = 'aaa'
|
|
c_b = 'bbb'
|
|
[[p_b]]
|
|
c_a = 'aaaa'
|
|
c_b = 'bbbb'
|
|
",
|
|
Parent<CasedString>,
|
|
"\
|
|
TOML parse error at line 8, column 13
|
|
|
|
|
8 | c_d = 'dd' # unknown field
|
|
| ^^^
|
|
unknown field `c_d`, expected `c_a` or `c_b`
|
|
"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn serde_derive_deserialize_errors() {
|
|
bad!(
|
|
"
|
|
p_a = ''
|
|
# ^
|
|
",
|
|
Parent<String>,
|
|
"\
|
|
TOML parse error at line 1, column 1
|
|
|
|
|
1 |
|
|
| ^
|
|
missing field `p_b`
|
|
"
|
|
);
|
|
|
|
bad!(
|
|
"
|
|
p_a = ''
|
|
p_b = [
|
|
{c_a = ''}
|
|
# ^
|
|
]
|
|
",
|
|
Parent<String>,
|
|
"\
|
|
TOML parse error at line 4, column 17
|
|
|
|
|
4 | {c_a = ''}
|
|
| ^^^^^^^^^^
|
|
missing field `c_b`
|
|
"
|
|
);
|
|
|
|
bad!(
|
|
"
|
|
p_a = ''
|
|
p_b = [
|
|
{c_a = '', c_b = 1}
|
|
# ^
|
|
]
|
|
",
|
|
Parent<String>,
|
|
"\
|
|
TOML parse error at line 4, column 34
|
|
|
|
|
4 | {c_a = '', c_b = 1}
|
|
| ^
|
|
invalid type: integer `1`, expected a string
|
|
"
|
|
);
|
|
|
|
// FIXME: This location could be better.
|
|
bad!(
|
|
"
|
|
p_a = ''
|
|
p_b = [
|
|
{c_a = '', c_b = '', c_d = ''},
|
|
# ^
|
|
]
|
|
",
|
|
Parent<String>,
|
|
"\
|
|
TOML parse error at line 4, column 38
|
|
|
|
|
4 | {c_a = '', c_b = '', c_d = ''},
|
|
| ^^^
|
|
unknown field `c_d`, expected `c_a` or `c_b`
|
|
"
|
|
);
|
|
|
|
bad!(
|
|
"
|
|
p_a = 'a'
|
|
p_b = [
|
|
{c_a = '', c_b = 1, c_d = ''},
|
|
# ^
|
|
]
|
|
",
|
|
Parent<String>,
|
|
"\
|
|
TOML parse error at line 4, column 34
|
|
|
|
|
4 | {c_a = '', c_b = 1, c_d = ''},
|
|
| ^
|
|
invalid type: integer `1`, expected a string
|
|
"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn error_handles_crlf() {
|
|
bad!(
|
|
"\r\n\
|
|
[t1]\r\n\
|
|
[t2]\r\n\
|
|
a = 1\r\n\
|
|
a = 2\r\n\
|
|
",
|
|
toml::Value,
|
|
"\
|
|
TOML parse error at line 5, column 1
|
|
|
|
|
5 | a = 2
|
|
| ^
|
|
duplicate key `a` in table `t2`
|
|
"
|
|
);
|
|
|
|
// Should be the same as above.
|
|
bad!(
|
|
"\n\
|
|
[t1]\n\
|
|
[t2]\n\
|
|
a = 1\n\
|
|
a = 2\n\
|
|
",
|
|
toml::Value,
|
|
"\
|
|
TOML parse error at line 5, column 1
|
|
|
|
|
5 | a = 2
|
|
| ^
|
|
duplicate key `a` in table `t2`
|
|
"
|
|
);
|
|
}
|