diff options
-rw-r--r-- | src/de.rs | 210 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/ser.rs | 6 | ||||
-rw-r--r-- | src/spanned.rs | 41 | ||||
-rw-r--r-- | test-suite/Cargo.toml | 2 | ||||
-rw-r--r-- | test-suite/tests/datetime.rs | 56 | ||||
-rw-r--r-- | test-suite/tests/de-errors.rs | 325 | ||||
-rw-r--r-- | test-suite/tests/invalid-misc.rs | 7 | ||||
-rw-r--r-- | test-suite/tests/invalid.rs | 10 | ||||
-rw-r--r-- | test-suite/tests/parser.rs | 37 | ||||
-rw-r--r-- | test-suite/tests/serde.rs | 4 |
11 files changed, 565 insertions, 134 deletions
@@ -29,7 +29,7 @@ where { match str::from_utf8(bytes) { Ok(s) => from_str(s), - Err(e) => Err(Error::custom(e.to_string())), + Err(e) => Err(Error::custom(None, e.to_string())), } } @@ -87,6 +87,7 @@ struct ErrorInner { kind: ErrorKind, line: Option<usize>, col: usize, + at: Option<usize>, message: String, key: Vec<String>, } @@ -209,7 +210,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { { let mut tables = self.tables()?; - visitor.visit_map(MapVisitor { + let res = visitor.visit_map(MapVisitor { values: Vec::new().into_iter(), next_value: None, depth: 0, @@ -219,6 +220,17 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { tables: &mut tables, array: false, de: self, + }); + res.map_err(|mut err| { + // Errors originating from this library (toml), have an offset + // attached to them already. Other errors, like those originating + // from serde (like "missing field") or from a custom deserializer, + // do not have offsets on them. Here, we do a best guess at their + // location, by attributing them to the "current table" (the last + // item in `tables`). + err.fix_offset(|| tables.last().map(|table| table.at)); + err.fix_linecol(|at| self.to_linecol(at)); + err }) } @@ -237,14 +249,17 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { E::String(val) => visitor.visit_enum(val.into_deserializer()), E::InlineTable(values) => { if values.len() != 1 { - Err(Error::from_kind(ErrorKind::Wanted { - expected: "exactly 1 element", - found: if values.is_empty() { - "zero elements" - } else { - "more than 1 element" + Err(Error::from_kind( + Some(value.start), + ErrorKind::Wanted { + expected: "exactly 1 element", + found: if values.is_empty() { + "zero elements" + } else { + "more than 1 element" + }, }, - })) + )) } else { visitor.visit_enum(InlineTableDeserializer { values: values.into_iter(), @@ -256,10 +271,13 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { name: name.expect("Expected table header to be passed."), value: value, }), - e @ _ => Err(Error::from_kind(ErrorKind::Wanted { - expected: "string or table", - found: e.type_name(), - })), + e @ _ => Err(Error::from_kind( + Some(value.start), + ErrorKind::Wanted { + expected: "string or table", + found: e.type_name(), + }, + )), } } @@ -278,7 +296,7 @@ struct Table<'a> { } #[doc(hidden)] -pub struct MapVisitor<'de: 'b, 'b> { +pub struct MapVisitor<'de, 'b> { values: vec::IntoIter<(Cow<'de, str>, Value<'de>)>, next_value: Option<(Cow<'de, str>, Value<'de>)>, depth: usize, @@ -556,7 +574,8 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { where V: de::Visitor<'de>, { - match self.value.e { + let start = self.value.start; + let res = match self.value.e { E::Integer(i) => visitor.visit_i64(i), E::Boolean(b) => visitor.visit_bool(b), E::Float(f) => visitor.visit_f64(f), @@ -578,7 +597,12 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { next_value: None, }) } - } + }; + res.map_err(|mut err| { + // Attribute the error to whatever value returned the error. + err.fix_offset(|| Some(start)); + err + }) } fn deserialize_struct<V>( @@ -615,13 +639,16 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { .collect::<Vec<Cow<'de, str>>>(); if !extra_fields.is_empty() { - return Err(Error::from_kind(ErrorKind::UnexpectedKeys { - keys: extra_fields - .iter() - .map(|k| k.to_string()) - .collect::<Vec<_>>(), - available: fields, - })); + return Err(Error::from_kind( + Some(self.value.start), + ErrorKind::UnexpectedKeys { + keys: extra_fields + .iter() + .map(|k| k.to_string()) + .collect::<Vec<_>>(), + available: fields, + }, + )); } } _ => {} @@ -664,14 +691,17 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { E::String(val) => visitor.visit_enum(val.into_deserializer()), E::InlineTable(values) => { if values.len() != 1 { - Err(Error::from_kind(ErrorKind::Wanted { - expected: "exactly 1 element", - found: if values.is_empty() { - "zero elements" - } else { - "more than 1 element" + Err(Error::from_kind( + Some(self.value.start), + ErrorKind::Wanted { + expected: "exactly 1 element", + found: if values.is_empty() { + "zero elements" + } else { + "more than 1 element" + }, }, - })) + )) } else { visitor.visit_enum(InlineTableDeserializer { values: values.into_iter(), @@ -679,10 +709,13 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { }) } } - e @ _ => Err(Error::from_kind(ErrorKind::Wanted { - expected: "string or inline table", - found: e.type_name(), - })), + e @ _ => Err(Error::from_kind( + Some(self.value.start), + ErrorKind::Wanted { + expected: "string or inline table", + found: e.type_name(), + }, + )), } } @@ -860,10 +893,13 @@ impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { let (key, value) = match self.values.next() { Some(pair) => pair, None => { - return Err(Error::from_kind(ErrorKind::Wanted { - expected: "table with exactly 1 entry", - found: "empty table", - })) + return Err(Error::from_kind( + None, // FIXME: How do we get an offset here? + ErrorKind::Wanted { + expected: "table with exactly 1 entry", + found: "empty table", + }, + )); } }; @@ -886,13 +922,19 @@ impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { if values.len() == 0 { Ok(()) } else { - Err(Error::from_kind(ErrorKind::ExpectedEmptyTable)) + Err(Error::from_kind( + Some(self.value.start), + ErrorKind::ExpectedEmptyTable, + )) } } - e @ _ => Err(Error::from_kind(ErrorKind::Wanted { - expected: "table", - found: e.type_name(), - })), + e @ _ => Err(Error::from_kind( + Some(self.value.start), + ErrorKind::Wanted { + expected: "table", + found: e.type_name(), + }, + )), } } @@ -914,10 +956,13 @@ impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { .enumerate() .map(|(index, (key, value))| match key.parse::<usize>() { Ok(key_index) if key_index == index => Ok(value), - Ok(_) | Err(_) => Err(Error::from_kind(ErrorKind::ExpectedTupleIndex { - expected: index, - found: key.to_string(), - })), + Ok(_) | Err(_) => Err(Error::from_kind( + Some(value.start), + ErrorKind::ExpectedTupleIndex { + expected: index, + found: key.to_string(), + }, + )), }) // Fold all values into a `Vec`, or return the first error. .fold(Ok(Vec::with_capacity(len)), |result, value_result| { @@ -941,13 +986,19 @@ impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { visitor, ) } else { - Err(Error::from_kind(ErrorKind::ExpectedTuple(len))) + Err(Error::from_kind( + Some(self.value.start), + ErrorKind::ExpectedTuple(len), + )) } } - e @ _ => Err(Error::from_kind(ErrorKind::Wanted { - expected: "table", - found: e.type_name(), - })), + e @ _ => Err(Error::from_kind( + Some(self.value.start), + ErrorKind::Wanted { + expected: "table", + found: e.type_name(), + }, + )), } } @@ -1195,17 +1246,20 @@ impl<'a> Deserializer<'a> { /// structures (tuple, newtype, struct) must be represented as a table. fn string_or_table(&mut self) -> Result<(Value<'a>, Option<Cow<'a, str>>), Error> { match self.peek()? { - Some((_, Token::LeftBracket)) => { + Some((span, Token::LeftBracket)) => { let tables = self.tables()?; if tables.len() != 1 { - return Err(Error::from_kind(ErrorKind::Wanted { - expected: "exactly 1 table", - found: if tables.is_empty() { - "zero tables" - } else { - "more than 1 table" + return Err(Error::from_kind( + Some(span.start), + ErrorKind::Wanted { + expected: "exactly 1 table", + found: if tables.is_empty() { + "zero tables" + } else { + "more than 1 table" + }, }, - })); + )); } let table = tables @@ -1710,10 +1764,8 @@ impl<'a> Deserializer<'a> { } fn error(&self, at: usize, kind: ErrorKind) -> Error { - let mut err = Error::from_kind(kind); - let (line, col) = self.to_linecol(at); - err.inner.line = Some(line); - err.inner.col = col; + let mut err = Error::from_kind(Some(at), kind); + err.fix_linecol(|at| self.to_linecol(at)); err } @@ -1740,24 +1792,26 @@ impl Error { self.inner.line.map(|line| (line, self.inner.col)) } - fn from_kind(kind: ErrorKind) -> Error { + fn from_kind(at: Option<usize>, kind: ErrorKind) -> Error { Error { inner: Box::new(ErrorInner { kind: kind, line: None, col: 0, + at, message: String::new(), key: Vec::new(), }), } } - fn custom(s: String) -> Error { + fn custom(at: Option<usize>, s: String) -> Error { Error { inner: Box::new(ErrorInner { kind: ErrorKind::Custom, line: None, col: 0, + at, message: s, key: Vec::new(), }), @@ -1777,6 +1831,28 @@ impl std::convert::From<Error> for std::io::Error { fn from(e: Error) -> Self { return std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()) } + + fn fix_offset<F>(&mut self, f: F) -> () + where + F: FnOnce() -> Option<usize>, + { + // An existing offset is always better positioned than anything we + // might want to add later. + if self.inner.at.is_none() { + self.inner.at = f(); + } + } + + fn fix_linecol<F>(&mut self, f: F) -> () + where + F: FnOnce(usize) -> (usize, usize), + { + if let Some(at) = self.inner.at { + let (line, col) = f(at); + self.inner.line = Some(line); + self.inner.col = col; + } + } } impl fmt::Display for Error { @@ -1892,7 +1968,7 @@ impl error::Error for Error { impl de::Error for Error { fn custom<T: fmt::Display>(msg: T) -> Error { - Error::custom(msg.to_string()) + Error::custom(None, msg.to_string()) } } @@ -166,5 +166,4 @@ mod tokens; pub mod macros; mod spanned; -#[doc(no_inline)] pub use crate::spanned::Spanned; @@ -222,7 +222,7 @@ enum State<'a> { } #[doc(hidden)] -pub struct SerializeSeq<'a: 'b, 'b> { +pub struct SerializeSeq<'a, 'b> { ser: &'b mut Serializer<'a>, first: Cell<bool>, type_: Cell<Option<&'static str>>, @@ -230,7 +230,7 @@ pub struct SerializeSeq<'a: 'b, 'b> { } #[doc(hidden)] -pub enum SerializeTable<'a: 'b, 'b> { +pub enum SerializeTable<'a, 'b> { Datetime(&'b mut Serializer<'a>), Table { ser: &'b mut Serializer<'a>, @@ -1186,7 +1186,7 @@ impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> { } } -struct DateStrEmitter<'a: 'b, 'b>(&'b mut Serializer<'a>); +struct DateStrEmitter<'a, 'b>(&'b mut Serializer<'a>); impl<'a, 'b> ser::Serializer for DateStrEmitter<'a, 'b> { type Ok = (); diff --git a/src/spanned.rs b/src/spanned.rs index cd02c87..7cccb32 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -1,23 +1,3 @@ -//! ``` -//! use serde_derive::Deserialize; -//! use toml::Spanned; -//! -//! #[derive(Deserialize)] -//! struct Value { -//! s: Spanned<String>, -//! } -//! -//! fn main() { -//! let t = "s = \"value\"\n"; -//! -//! let u: Value = toml::from_str(t).unwrap(); -//! -//! assert_eq!(u.s.start(), 4); -//! assert_eq!(u.s.end(), 11); -//! assert_eq!(u.s.get_ref(), "value"); -//! assert_eq!(u.s.into_inner(), String::from("value")); -//! } -//! ``` use serde::{de, ser}; use std::fmt; @@ -32,6 +12,27 @@ pub const END: &'static str = "$__toml_private_end"; pub const VALUE: &'static str = "$__toml_private_value"; /// A spanned value, indicating the range at which it is defined in the source. +/// +/// ``` +/// use serde_derive::Deserialize; +/// use toml::Spanned; +/// +/// #[derive(Deserialize)] +/// struct Value { +/// s: Spanned<String>, +/// } +/// +/// fn main() { +/// let t = "s = \"value\"\n"; +/// +/// let u: Value = toml::from_str(t).unwrap(); +/// +/// assert_eq!(u.s.start(), 4); +/// assert_eq!(u.s.end(), 11); +/// assert_eq!(u.s.get_ref(), "value"); +/// assert_eq!(u.s.into_inner(), String::from("value")); +/// } +/// ``` #[derive(Debug)] pub struct Spanned<T> { /// The start range. diff --git a/test-suite/Cargo.toml b/test-suite/Cargo.toml index 4f4db16..9f500b3 100644 --- a/test-suite/Cargo.toml +++ b/test-suite/Cargo.toml @@ -7,6 +7,6 @@ edition = "2018" [dev-dependencies] toml = { path = ".." } -serde = "1.0" +serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0" serde_json = "1.0" diff --git a/test-suite/tests/datetime.rs b/test-suite/tests/datetime.rs index 4855f54..74b5939 100644 --- a/test-suite/tests/datetime.rs +++ b/test-suite/tests/datetime.rs @@ -42,75 +42,93 @@ fn times() { #[test] fn bad_times() { - bad!("foo = 199-09-09", "failed to parse datetime for key `foo`"); - bad!("foo = 199709-09", "failed to parse datetime for key `foo`"); - bad!("foo = 1997-9-09", "failed to parse datetime for key `foo`"); - bad!("foo = 1997-09-9", "failed to parse datetime for key `foo`"); + bad!( + "foo = 199-09-09", + "failed to parse datetime for key `foo` at line 1 column 7" + ); + bad!( + "foo = 199709-09", + "failed to parse datetime for key `foo` at line 1 column 7" + ); + bad!( + "foo = 1997-9-09", + "failed to parse datetime for key `foo` at line 1 column 7" + ); + bad!( + "foo = 1997-09-9", + "failed to parse datetime for key `foo` at line 1 column 7" + ); bad!( "foo = 1997-09-0909:09:09", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.", "invalid date at line 1 column 7" ); - bad!("foo = T", "failed to parse datetime for key `foo`"); + bad!( + "foo = T", + "failed to parse datetime for key `foo` at line 1 column 7" + ); bad!( "foo = T.", "expected newline, found a period at line 1 column 8" ); - bad!("foo = TZ", "failed to parse datetime for key `foo`"); + bad!( + "foo = TZ", + "failed to parse datetime for key `foo` at line 1 column 7" + ); bad!( "foo = 1997-09-09T09:09:09.09+", "invalid date at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09+09", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09+09:9", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09+0909", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09-", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09-09", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09-09:9", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T09:09:09.09-0909", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-00-09T09:09:09.09Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-00T09:09:09.09Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T30:09:09.09Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T12:69:09.09Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 1997-09-09T12:09:69.09Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); } diff --git a/test-suite/tests/de-errors.rs b/test-suite/tests/de-errors.rs new file mode 100644 index 0000000..7cceb7b --- /dev/null +++ b/test-suite/tests/de-errors.rs @@ -0,0 +1,325 @@ +extern crate serde; +extern crate toml; + +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) => assert_eq!(e.to_string(), $msg), + } + }; +} + +#[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>, + "invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19" + ); + + // Missing field in table. + bad!( + " + p_a = 'a' + # ^ + ", + Parent<CasedString>, + "missing field `p_b` at line 1 column 1" + ); + + // Invalid type in p_b. + bad!( + " + p_a = 'a' + p_b = 1 + # ^ + ", + Parent<CasedString>, + "invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19" + ); + + // Sub-table in Vec is missing a field. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a'} + # ^ + ] + ", + Parent<CasedString>, + "missing field `c_b` for key `p_b` at line 4 column 17" + ); + + // Sub-table in Vec has a field with a bad value. + bad!( + " + p_a = 'a' + p_b = [ + {c_a = 'a', c_b = '*'} + # ^ + ] + ", + Parent<CasedString>, + "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b` at line 4 column 35" + ); + + // 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>, + "missing field `c_b` for key `p_b` at line 5 column 17" + ); + + // 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>, + "missing field `c_b` for key `p_b` at line 5 column 17" + ); + + // 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>, + "invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36" + ); + + // Sub-table in the middle of a Vec has an extra field. + // FIXME: This location could be better. + 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>, + "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17" + ); + + // 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>, + "missing field `c_b` for key `p_b` at line 12 column 13" + ); + + // 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>, + "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19" + ); + + // Sub-table in the middle of a Vec has an extra 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_d = 'dd' # unknown field + [[p_b]] + c_a = 'aaa' + c_b = 'bbb' + [[p_b]] + # ^ + c_a = 'aaaa' + c_b = 'bbbb' + ", + Parent<CasedString>, + "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13" + ); +} + +#[test] +fn serde_derive_deserialize_errors() { + bad!( + " + p_a = '' + # ^ + ", + Parent<String>, + "missing field `p_b` at line 1 column 1" + ); + + bad!( + " + p_a = '' + p_b = [ + {c_a = ''} + # ^ + ] + ", + Parent<String>, + "missing field `c_b` for key `p_b` at line 4 column 17" + ); + + bad!( + " + p_a = '' + p_b = [ + {c_a = '', c_b = 1} + # ^ + ] + ", + Parent<String>, + "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34" + ); + + // FIXME: This location could be better. + bad!( + " + p_a = '' + p_b = [ + {c_a = '', c_b = '', c_d = ''}, + # ^ + ] + ", + Parent<String>, + "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17" + ); + + bad!( + " + p_a = 'a' + p_b = [ + {c_a = '', c_b = 1, c_d = ''}, + # ^ + ] + ", + Parent<String>, + "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34" + ); +} diff --git a/test-suite/tests/invalid-misc.rs b/test-suite/tests/invalid-misc.rs index f18012a..cea0801 100644 --- a/test-suite/tests/invalid-misc.rs +++ b/test-suite/tests/invalid-misc.rs @@ -27,14 +27,17 @@ fn bad() { ); bad!("a = -0x1", "invalid number at line 1 column 5"); - bad!("a = 0x-1", "failed to parse datetime for key `a`"); + bad!( + "a = 0x-1", + "failed to parse datetime for key `a` at line 1 column 5" + ); // Dotted keys. bad!( "a.b.c = 1 a.b = 2 ", - "duplicate key: `b` for key `a`" + "duplicate key: `b` for key `a` at line 1 column 9" ); bad!( "a = 1 diff --git a/test-suite/tests/invalid.rs b/test-suite/tests/invalid.rs index bfde2d4..3312629 100644 --- a/test-suite/tests/invalid.rs +++ b/test-suite/tests/invalid.rs @@ -32,7 +32,7 @@ test!( test!( datetime_malformed_no_leads, include_str!("invalid/datetime-malformed-no-leads.toml"), - "failed to parse datetime for key `no-leads`" + "failed to parse datetime for key `no-leads` at line 1 column 12" ); test!( datetime_malformed_no_secs, @@ -42,22 +42,22 @@ test!( test!( datetime_malformed_no_t, include_str!("invalid/datetime-malformed-no-t.toml"), - "failed to parse datetime for key `no-t`" + "failed to parse datetime for key `no-t` at line 1 column 8" ); test!( datetime_malformed_with_milli, include_str!("invalid/datetime-malformed-with-milli.toml"), - "failed to parse datetime for key `with-milli`" + "failed to parse datetime for key `with-milli` at line 1 column 14" ); test!( duplicate_key_table, include_str!("invalid/duplicate-key-table.toml"), - "duplicate key: `type` for key `fruit`" + "duplicate key: `type` for key `fruit` at line 4 column 1" ); test!( duplicate_keys, include_str!("invalid/duplicate-keys.toml"), - "duplicate key: `dupe`" + "duplicate key: `dupe` at line 1 column 1" ); test!( duplicate_table, diff --git a/test-suite/tests/parser.rs b/test-suite/tests/parser.rs index 1a684c5..012bd65 100644 --- a/test-suite/tests/parser.rs +++ b/test-suite/tests/parser.rs @@ -453,7 +453,10 @@ fn inline_tables() { "a = {,}", "expected a table key, found a comma at line 1 column 6" ); - bad!("a = {a=1,a=1}", "duplicate key: `a` for key `a`"); + bad!( + "a = {a=1,a=1}", + "duplicate key: `a` for key `a` at line 1 column 5" + ); bad!( "a = {\n}", "expected a table key, found a newline at line 1 column 6" @@ -533,9 +536,15 @@ fn booleans() { let table = "foo = false".parse::<Value>().unwrap(); assert_eq!(table["foo"].as_bool(), Some(false)); - bad!("foo = true2", "failed to parse datetime for key `foo`"); + bad!( + "foo = true2", + "failed to parse datetime for key `foo` at line 1 column 7" + ); bad!("foo = false2", "invalid number at line 1 column 7"); - bad!("foo = t1", "failed to parse datetime for key `foo`"); + bad!( + "foo = t1", + "failed to parse datetime for key `foo` at line 1 column 7" + ); bad!("foo = f2", "invalid number at line 1 column 7"); } @@ -547,28 +556,28 @@ fn bad_nesting() { [[a]] b = 5 ", - "duplicate key: `a`" + "duplicate key: `a` at line 3 column 9" ); bad!( " a = 1 [a.b] ", - "duplicate key: `a`" + "duplicate key: `a` at line 3 column 9" ); bad!( " a = [] [a.b] ", - "duplicate key: `a`" + "duplicate key: `a` at line 3 column 9" ); bad!( " a = [] [[a.b]] ", - "duplicate key: `a`" + "duplicate key: `a` at line 3 column 9" ); bad!( " @@ -577,7 +586,7 @@ fn bad_nesting() { [a.b] c = 2 ", - "duplicate key: `b` for key `a`" + "duplicate key: `b` for key `a` at line 4 column 9" ); } @@ -608,7 +617,7 @@ fn bad_table_redefine() { b = {} [a.b] ", - "duplicate key: `b` for key `a`" + "duplicate key: `b` for key `a` at line 4 column 9" ); bad!( @@ -637,23 +646,23 @@ fn datetimes() { t!("2016-09-09T09:09:09.123456789-02:00"); bad!( "foo = 2016-09-09T09:09:09.Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 2016-9-09T09:09:09Z", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 2016-09-09T09:09:09+2:00", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 2016-09-09T09:09:09-2:00", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); bad!( "foo = 2016-09-09T09:09:09Z-2:00", - "failed to parse datetime for key `foo`" + "failed to parse datetime for key `foo` at line 1 column 7" ); } diff --git a/test-suite/tests/serde.rs b/test-suite/tests/serde.rs index 95b8bc4..56172bd 100644 --- a/test-suite/tests/serde.rs +++ b/test-suite/tests/serde.rs @@ -273,7 +273,7 @@ fn type_errors() { Table(map! { bar: Value::String("a".to_string()) }), - "invalid type: string \"a\", expected isize for key `bar`", + "invalid type: string \"a\", expected isize for key `bar` at line 1 column 7", "invalid type: string \"a\", expected isize for key `bar`" } @@ -290,7 +290,7 @@ fn type_errors() { bar: Value::String("a".to_string()) }) }), - "invalid type: string \"a\", expected isize for key `foo.bar`", + "invalid type: string \"a\", expected isize for key `foo.bar` at line 2 column 7", "invalid type: string \"a\", expected isize for key `foo.bar`" } } |