diff --git a/Cargo.lock b/Cargo.lock index 5e212963..de16d1a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,6 +897,7 @@ dependencies = [ "snapbox", "toml-test-harness", "toml_datetime", + "toml_edit", ] [[package]] diff --git a/crates/toml/Cargo.toml b/crates/toml/Cargo.toml index 267b2d51..22a353a8 100644 --- a/crates/toml/Cargo.toml +++ b/crates/toml/Cargo.toml @@ -46,6 +46,7 @@ preserve_order = ["indexmap"] [dependencies] serde = "1.0.145" indexmap = { version = "1.9.1", optional = true } +toml_edit = { version = "0.17.1", path = "../toml_edit", features = ["serde"] } toml_datetime = { version = "0.5.0", path = "../toml_datetime", features = ["serde"] } serde_spanned = { version = "0.5.0", path = "../serde_spanned", features = ["serde"] } diff --git a/crates/toml/examples/toml2json.rs b/crates/toml/examples/toml2json.rs index 1b90c9fd..3660611c 100644 --- a/crates/toml/examples/toml2json.rs +++ b/crates/toml/examples/toml2json.rs @@ -13,7 +13,7 @@ fn main() { let mut input = String::new(); if args.len() > 1 { let name = args.nth(1).unwrap(); - File::open(&name) + File::open(name) .and_then(|mut f| f.read_to_string(&mut input)) .unwrap(); } else { diff --git a/crates/toml/src/de.rs b/crates/toml/src/de.rs index b2b931de..335009e4 100644 --- a/crates/toml/src/de.rs +++ b/crates/toml/src/de.rs @@ -4,40 +4,6 @@ //! into Rust structures. Note that some top-level functions here are also //! provided at the top of the crate. -use std::borrow::Cow; -use std::collections::HashMap; -use std::error; -use std::f64; -use std::fmt; -use std::iter; -use std::marker::PhantomData; -use std::str; -use std::vec; - -use serde::de; -use serde::de::value::BorrowedStrDeserializer; -use serde::de::IntoDeserializer; -use toml_datetime::__unstable as datetime; - -use crate::tokens::{Error as TokenError, Span, Token, Tokenizer}; - -/// Type Alias for a TOML Table pair -type TablePair<'a> = ((Span, Cow<'a, str>), Value<'a>); - -/// Deserializes a byte slice into a type. -/// -/// This function will attempt to interpret `bytes` as UTF-8 data and then -/// deserialize `T` from the TOML document provided. -pub fn from_slice<'de, T>(bytes: &'de [u8]) -> Result -where - T: de::Deserialize<'de>, -{ - match str::from_utf8(bytes) { - Ok(s) => from_str(s), - Err(e) => Err(Error::custom(None, e.to_string())), - } -} - /// Deserializes a string into a type. /// /// This function will attempt to interpret `s` as a TOML document and @@ -69,2205 +35,252 @@ where /// assert_eq!(config.title, "TOML Example"); /// assert_eq!(config.owner.name, "Lisa"); /// ``` -pub fn from_str<'de, T>(s: &'de str) -> Result +pub fn from_str(s: &'_ str) -> Result where - T: de::Deserialize<'de>, + T: serde::de::DeserializeOwned, { - let mut d = Deserializer::new(s); - let ret = T::deserialize(&mut d)?; - d.end()?; - Ok(ret) + T::deserialize(Deserializer::new(s)) } /// Errors that can occur when deserializing a type. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Error { - inner: Box, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -struct ErrorInner { - kind: ErrorKind, - line: Option, - col: usize, - at: Option, - message: String, - key: Vec, -} - -/// Errors that can occur when deserializing a type. -#[derive(Debug, PartialEq, Eq, Clone)] -#[non_exhaustive] -enum ErrorKind { - /// EOF was reached when looking for a value - UnexpectedEof, - - /// An invalid character not allowed in a string was found - InvalidCharInString(char), - - /// An invalid character was found as an escape - InvalidEscape(char), - - /// An invalid character was found in a hex escape - InvalidHexEscape(char), - - /// An invalid escape value was specified in a hex escape in a string. - /// - /// Valid values are in the plane of unicode codepoints. - InvalidEscapeValue(u32), - - /// A newline in a string was encountered when one was not allowed. - NewlineInString, - - /// An unexpected character was encountered, typically when looking for a - /// value. - Unexpected(char), - - /// An unterminated string was found where EOF was found before the ending - /// EOF mark. - UnterminatedString, - - /// A newline was found in a table key. - NewlineInTableKey, - - /// A number failed to parse - NumberInvalid, - - /// A date or datetime was invalid - DateInvalid, - - /// Wanted one sort of token, but found another. - Wanted { - /// Expected token type - expected: &'static str, - /// Actually found token type - found: &'static str, - }, - - /// A duplicate table definition was found. - DuplicateTable(String), - - /// A previously defined table was redefined as an array. - RedefineAsArray, - - /// An empty table key was found. - EmptyTableKey, - - /// Multiline strings are not allowed for key - MultilineStringKey, - - /// A custom error which could be generated when deserializing a particular - /// type. - Custom, - - /// A tuple with a certain number of elements was expected but something - /// else was found. - ExpectedTuple(usize), - - /// Expected table keys to be in increasing tuple index order, but something - /// else was found. - ExpectedTupleIndex { - /// Expected index. - expected: usize, - /// Key that was specified. - found: String, - }, - - /// An empty table was expected but entries were found - ExpectedEmptyTable, - - /// Dotted key attempted to extend something that is not a table. - DottedKeyInvalidType, - - /// An unexpected key was encountered. - /// - /// Used when deserializing a struct with a limited set of fields. - UnexpectedKeys { - /// The unexpected keys. - keys: Vec, - /// Keys that may be specified. - available: &'static [&'static str], - }, - - /// Unquoted string was found when quoted one was expected - UnquotedString, -} - -/// Deserialization implementation for TOML. -pub struct Deserializer<'a> { - require_newline_after_table: bool, - allow_duplciate_after_longer_table: bool, - input: &'a str, - tokens: Tokenizer<'a>, -} - -impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - let mut tables = self.tables()?; - let table_indices = build_table_indices(&tables); - let table_pindices = build_table_pindices(&tables); - - let res = visitor.visit_map(MapVisitor { - values: Vec::new().into_iter().peekable(), - next_value: None, - depth: 0, - cur: 0, - cur_parent: 0, - max: tables.len(), - table_indices: &table_indices, - table_pindices: &table_pindices, - 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 - }) - } - - // Called when the type to deserialize is an enum, as opposed to a field in the type. - fn deserialize_enum( - self, - _name: &'static str, - _variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - let (value, name) = self.string_or_table()?; - match value.e { - E::String(val) => visitor.visit_enum(val.into_deserializer()), - E::InlineTable(values) => { - if values.len() != 1 { - 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(), - next_value: None, - }) - } - } - E::DottedTable(_) => visitor.visit_enum(DottedTableDeserializer { - name: name.expect("Expected table header to be passed."), - value, - }), - e => Err(Error::from_kind( - Some(value.start), - ErrorKind::Wanted { - expected: "string or table", - found: e.type_name(), - }, - )), - } - } - - fn deserialize_struct( - self, - name: &'static str, - fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - if name == serde_spanned::NAME - && fields - == [ - serde_spanned::START_FIELD, - serde_spanned::END_FIELD, - serde_spanned::VALUE_FIELD, - ] - { - let start = 0; - let end = self.input.len(); - - let res = visitor.visit_map(SpannedDeserializer { - phantom_data: PhantomData, - start: Some(start), - value: Some(self), - end: Some(end), - }); - return res; - } - - self.deserialize_any(visitor) - } - - serde::forward_to_deserialize_any! { - bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map unit newtype_struct - ignored_any unit_struct tuple_struct tuple option identifier - } + inner: toml_edit::de::Error, } -// Builds a datastructure that allows for efficient sublinear lookups. -// The returned HashMap contains a mapping from table header (like [a.b.c]) -// to list of tables with that precise name. The tables are being identified -// by their index in the passed slice. We use a list as the implementation -// uses this data structure for arrays as well as tables, -// so if any top level [[name]] array contains multiple entries, -// there are multiple entries in the list. -// The lookup is performed in the `SeqAccess` implementation of `MapVisitor`. -// The lists are ordered, which we exploit in the search code by using -// bisection. -fn build_table_indices<'de>(tables: &[Table<'de>]) -> HashMap>, Vec> { - let mut res = HashMap::new(); - for (i, table) in tables.iter().enumerate() { - let header = table.header.iter().map(|v| v.1.clone()).collect::>(); - res.entry(header).or_insert_with(Vec::new).push(i); +impl Error { + fn new(inner: toml_edit::de::Error) -> Self { + Self { inner } } - res -} -// Builds a datastructure that allows for efficient sublinear lookups. -// The returned HashMap contains a mapping from table header (like [a.b.c]) -// to list of tables whose name at least starts with the specified -// name. So searching for [a.b] would give both [a.b.c.d] as well as [a.b.e]. -// The tables are being identified by their index in the passed slice. -// -// A list is used for two reasons: First, the implementation also -// stores arrays in the same data structure and any top level array -// of size 2 or greater creates multiple entries in the list with the -// same shared name. Second, there can be multiple tables sharing -// the same prefix. -// -// The lookup is performed in the `MapAccess` implementation of `MapVisitor`. -// The lists are ordered, which we exploit in the search code by using -// bisection. -fn build_table_pindices<'de>(tables: &[Table<'de>]) -> HashMap>, Vec> { - let mut res = HashMap::new(); - for (i, table) in tables.iter().enumerate() { - let header = table.header.iter().map(|v| v.1.clone()).collect::>(); - for len in 0..=header.len() { - res.entry(header[..len].to_owned()) - .or_insert_with(Vec::new) - .push(i); - } + /// The start/end index into the original document where the error occurred + pub fn span(&self) -> Option> { + self.inner.span() } - res -} -fn headers_equal<'a, 'b>(hdr_a: &[(Span, Cow<'a, str>)], hdr_b: &[(Span, Cow<'b, str>)]) -> bool { - if hdr_a.len() != hdr_b.len() { - return false; + /// Produces a (line, column) pair of the position of the error if available + /// + /// All indexes are 0-based. + #[deprecated(since = "0.18.0", note = "See instead `Error::span`")] + pub fn line_col(&self) -> Option<(usize, usize)> { + #[allow(deprecated)] + self.inner.line_col() } - hdr_a.iter().zip(hdr_b.iter()).all(|(h1, h2)| h1.1 == h2.1) -} - -struct Table<'a> { - at: usize, - header: Vec<(Span, Cow<'a, str>)>, - values: Option>>, - array: bool, -} - -struct MapVisitor<'de, 'b> { - values: iter::Peekable>>, - next_value: Option>, - depth: usize, - cur: usize, - cur_parent: usize, - max: usize, - table_indices: &'b HashMap>, Vec>, - table_pindices: &'b HashMap>, Vec>, - tables: &'b mut [Table<'de>], - array: bool, - de: &'b mut Deserializer<'de>, } -impl<'de, 'b> de::MapAccess<'de> for MapVisitor<'de, 'b> { - type Error = Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Error> +impl serde::de::Error for Error { + fn custom(msg: T) -> Self where - K: de::DeserializeSeed<'de>, + T: std::fmt::Display, { - if self.cur_parent == self.max || self.cur == self.max { - return Ok(None); - } - - loop { - assert!(self.next_value.is_none()); - if let Some((key, value)) = self.values.next() { - let ret = seed.deserialize(StrDeserializer::spanned(key.clone()))?; - self.next_value = Some((key, value)); - return Ok(Some(ret)); - } - - let next_table = { - let prefix_stripped = self.tables[self.cur_parent].header[..self.depth] - .iter() - .map(|v| v.1.clone()) - .collect::>(); - self.table_pindices - .get(&prefix_stripped) - .and_then(|entries| { - let start = entries.binary_search(&self.cur).unwrap_or_else(|v| v); - if start == entries.len() || entries[start] < self.cur { - return None; - } - entries[start..] - .iter() - .filter_map(|i| if *i < self.max { Some(*i) } else { None }) - .map(|i| (i, &self.tables[i])) - .find(|(_, table)| table.values.is_some()) - .map(|p| p.0) - }) - }; - - let pos = match next_table { - Some(pos) => pos, - None => return Ok(None), - }; - self.cur = pos; - - // Test to see if we're duplicating our parent's table, and if so - // then this is an error in the toml format - if self.cur_parent != pos { - if headers_equal( - &self.tables[self.cur_parent].header, - &self.tables[pos].header, - ) { - let at = self.tables[pos].at; - let name = self.tables[pos] - .header - .iter() - .map(|k| k.1.to_owned()) - .collect::>() - .join("."); - return Err(self.de.error(at, ErrorKind::DuplicateTable(name))); - } - - // If we're here we know we should share the same prefix, and if - // the longer table was defined first then we want to narrow - // down our parent's length if possible to ensure that we catch - // duplicate tables defined afterwards. - if !self.de.allow_duplciate_after_longer_table { - let parent_len = self.tables[self.cur_parent].header.len(); - let cur_len = self.tables[pos].header.len(); - if cur_len < parent_len { - self.cur_parent = pos; - } - } - } - - let table = &mut self.tables[pos]; - - // If we're not yet at the appropriate depth for this table then we - // just next the next portion of its header and then continue - // decoding. - if self.depth != table.header.len() { - let key = &table.header[self.depth]; - let key = seed.deserialize(StrDeserializer::spanned(key.clone()))?; - return Ok(Some(key)); - } - - // Rule out cases like: - // - // [[foo.bar]] - // [[foo]] - if table.array { - let kind = ErrorKind::RedefineAsArray; - return Err(self.de.error(table.at, kind)); - } - - self.values = table - .values - .take() - .expect("Unable to read table values") - .into_iter() - .peekable(); - } + Error::new(toml_edit::de::Error::custom(msg)) } +} - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - if let Some((k, v)) = self.next_value.take() { - match seed.deserialize(ValueDeserializer::new(v)) { - Ok(v) => return Ok(v), - Err(mut e) => { - e.add_key_context(&k.1); - return Err(e); - } - } - } - - let array = - self.tables[self.cur].array && self.depth == self.tables[self.cur].header.len() - 1; - self.cur += 1; - let res = seed.deserialize(MapVisitor { - values: Vec::new().into_iter().peekable(), - next_value: None, - depth: self.depth + if array { 0 } else { 1 }, - cur_parent: self.cur - 1, - cur: 0, - max: self.max, - array, - table_indices: self.table_indices, - table_pindices: self.table_pindices, - tables: &mut *self.tables, - de: &mut *self.de, - }); - res.map_err(|mut e| { - e.add_key_context(&self.tables[self.cur - 1].header[self.depth].1); - e - }) +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) } } -impl<'de, 'b> de::SeqAccess<'de> for MapVisitor<'de, 'b> { - type Error = Error; - - fn next_element_seed(&mut self, seed: K) -> Result, Error> - where - K: de::DeserializeSeed<'de>, - { - assert!(self.next_value.is_none()); - assert!(self.values.next().is_none()); - - if self.cur_parent == self.max { - return Ok(None); - } +impl std::error::Error for Error {} - let header_stripped = self.tables[self.cur_parent] - .header - .iter() - .map(|v| v.1.clone()) - .collect::>(); - let start_idx = self.cur_parent + 1; - let next = self - .table_indices - .get(&header_stripped) - .and_then(|entries| { - let start = entries.binary_search(&start_idx).unwrap_or_else(|v| v); - if start == entries.len() || entries[start] < start_idx { - return None; - } - entries[start..] - .iter() - .filter_map(|i| if *i < self.max { Some(*i) } else { None }) - .map(|i| (i, &self.tables[i])) - .find(|(_, table)| table.array) - .map(|p| p.0) - }) - .unwrap_or(self.max); +/// Deserialization TOML document +pub struct Deserializer<'a> { + input: &'a str, +} - let ret = seed.deserialize(MapVisitor { - values: self.tables[self.cur_parent] - .values - .take() - .expect("Unable to read table values") - .into_iter() - .peekable(), - next_value: None, - depth: self.depth + 1, - cur_parent: self.cur_parent, - max: next, - cur: 0, - array: false, - table_indices: self.table_indices, - table_pindices: self.table_pindices, - tables: self.tables, - de: self.de, - })?; - self.cur_parent = next; - Ok(Some(ret)) +impl<'a> Deserializer<'a> { + /// Deserialization implementation for TOML. + pub fn new(input: &'a str) -> Self { + Self { input } } } -impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> { +impl<'de, 'a> serde::Deserializer<'de> for Deserializer<'a> { type Error = Error; - fn deserialize_any(self, visitor: V) -> Result + fn deserialize_any(self, visitor: V) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - if self.array { - visitor.visit_seq(self) - } else { - visitor.visit_map(self) - } + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_any(visitor).map_err(Error::new) } // `None` is interpreted as a missing field so be sure to implement `Some` // as a present field. fn deserialize_option(self, visitor: V) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - visitor.visit_some(self) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_option(visitor).map_err(Error::new) } fn deserialize_newtype_struct( self, - _name: &'static str, + name: &'static str, visitor: V, ) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - visitor.visit_newtype_struct(self) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_newtype_struct(name, visitor) + .map_err(Error::new) } fn deserialize_struct( - mut self, + self, name: &'static str, fields: &'static [&'static str], visitor: V, ) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - if name == serde_spanned::NAME - && fields - == [ - serde_spanned::START_FIELD, - serde_spanned::END_FIELD, - serde_spanned::VALUE_FIELD, - ] - && !(self.array && self.values.peek().is_some()) - { - // TODO we can't actually emit spans here for the *entire* table/array - // due to the format that toml uses. Setting the start and end to 0 is - // *detectable* (and no reasonable span would look like that), - // it would be better to expose this in the API via proper - // ADTs like Option. - let start = 0; - let end = 0; - - let res = visitor.visit_map(SpannedDeserializer { - phantom_data: PhantomData, - start: Some(start), - value: Some(self), - end: Some(end), - }); - return res; - } - - self.deserialize_any(visitor) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_struct(name, fields, visitor) + .map_err(Error::new) } + // Called when the type to deserialize is an enum, as opposed to a field in the type. fn deserialize_enum( - self, - _name: &'static str, - _variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - if self.tables.len() != 1 { - return Err(Error::custom( - Some(self.cur), - "enum table must contain exactly one table".into(), - )); - } - let table = &mut self.tables[0]; - let values = table.values.take().expect("table has no values?"); - if table.header.is_empty() { - return Err(self.de.error(self.cur, ErrorKind::EmptyTableKey)); - } - let name = table.header[table.header.len() - 1].1.to_owned(); - visitor.visit_enum(DottedTableDeserializer { - name, - value: Value { - e: E::DottedTable(values), - start: 0, - end: 0, - }, - }) - } - - serde::forward_to_deserialize_any! { - bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map unit identifier - ignored_any unit_struct tuple_struct tuple - } -} - -struct StrDeserializer<'a> { - span: Option, - key: Cow<'a, str>, -} - -impl<'a> StrDeserializer<'a> { - fn spanned(inner: (Span, Cow<'a, str>)) -> StrDeserializer<'a> { - StrDeserializer { - span: Some(inner.0), - key: inner.1, - } - } - fn new(key: Cow<'a, str>) -> StrDeserializer<'a> { - StrDeserializer { span: None, key } - } -} - -impl<'a> de::IntoDeserializer<'a, Error> for StrDeserializer<'a> { - type Deserializer = Self; - - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -impl<'de> de::Deserializer<'de> for StrDeserializer<'de> { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.key { - Cow::Borrowed(s) => visitor.visit_borrowed_str(s), - Cow::Owned(s) => visitor.visit_string(s), - } - } - - fn deserialize_struct( self, name: &'static str, - fields: &'static [&'static str], + variants: &'static [&'static str], visitor: V, ) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - if name == serde_spanned::NAME - && fields - == [ - serde_spanned::START_FIELD, - serde_spanned::END_FIELD, - serde_spanned::VALUE_FIELD, - ] - { - if let Some(span) = self.span { - return visitor.visit_map(SpannedDeserializer { - phantom_data: PhantomData, - start: Some(span.start), - value: Some(StrDeserializer::new(self.key)), - end: Some(span.end), - }); - } - } - self.deserialize_any(visitor) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_enum(name, variants, visitor) + .map_err(Error::new) } serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map option unit newtype_struct - ignored_any unit_struct tuple_struct tuple enum identifier + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier } } -struct ValueDeserializer<'a> { - value: Value<'a>, - validate_struct_keys: bool, +/// Deserialization TOML value +pub struct ValueDeserializer<'a> { + input: &'a str, } impl<'a> ValueDeserializer<'a> { - fn new(value: Value<'a>) -> ValueDeserializer<'a> { - ValueDeserializer { - value, - validate_struct_keys: false, - } - } - - fn with_struct_key_validation(mut self) -> Self { - self.validate_struct_keys = true; - self + /// Deserialization implementation for TOML. + pub fn new(input: &'a str) -> Self { + Self { input } } } -impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { +impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer<'a> { type Error = Error; - fn deserialize_any(self, visitor: V) -> Result + fn deserialize_any(self, visitor: V) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - 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), - E::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s), - E::String(Cow::Owned(s)) => visitor.visit_string(s), - E::Datetime(s) => visitor.visit_map(DatetimeDeserializer { - date: s, - visited: false, - }), - E::Array(values) => { - let mut s = de::value::SeqDeserializer::new(values.into_iter()); - let ret = visitor.visit_seq(&mut s)?; - s.end()?; - Ok(ret) - } - E::InlineTable(values) | E::DottedTable(values) => { - visitor.visit_map(InlineTableDeserializer { - values: values.into_iter(), - 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( - self, - name: &'static str, - fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - if name == datetime::NAME && fields == [datetime::FIELD] { - if let E::Datetime(s) = self.value.e { - return visitor.visit_map(DatetimeDeserializer { - date: s, - visited: false, - }); - } - } - - if self.validate_struct_keys { - match self.value.e { - E::InlineTable(ref values) | E::DottedTable(ref values) => { - let extra_fields = values - .iter() - .filter_map(|key_value| { - let (ref key, ref _val) = *key_value; - if !fields.contains(&&*(key.1)) { - Some(key.clone()) - } else { - None - } - }) - .collect::>(); - - if !extra_fields.is_empty() { - return Err(Error::from_kind( - Some(self.value.start), - ErrorKind::UnexpectedKeys { - keys: extra_fields - .iter() - .map(|k| k.1.to_string()) - .collect::>(), - available: fields, - }, - )); - } - } - _ => {} - } - } - - if name == serde_spanned::NAME - && fields - == [ - serde_spanned::START_FIELD, - serde_spanned::END_FIELD, - serde_spanned::VALUE_FIELD, - ] - { - let start = self.value.start; - let end = self.value.end; - - return visitor.visit_map(SpannedDeserializer { - phantom_data: PhantomData, - start: Some(start), - value: Some(self.value), - end: Some(end), - }); - } - - self.deserialize_any(visitor) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_any(visitor).map_err(Error::new) } // `None` is interpreted as a missing field so be sure to implement `Some` // as a present field. fn deserialize_option(self, visitor: V) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - visitor.visit_some(self) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner.deserialize_option(visitor).map_err(Error::new) } - fn deserialize_enum( + fn deserialize_newtype_struct( self, - _name: &'static str, - _variants: &'static [&'static str], + name: &'static str, visitor: V, ) -> Result where - V: de::Visitor<'de>, + V: serde::de::Visitor<'de>, { - match self.value.e { - E::String(val) => visitor.visit_enum(val.into_deserializer()), - E::InlineTable(values) => { - if values.len() != 1 { - 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(), - next_value: None, - }) - } - } - e => Err(Error::from_kind( - Some(self.value.start), - ErrorKind::Wanted { - expected: "string or inline table", - found: e.type_name(), - }, - )), - } + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_newtype_struct(name, visitor) + .map_err(Error::new) } - fn deserialize_newtype_struct( + fn deserialize_struct( self, - _name: &'static str, + name: &'static str, + fields: &'static [&'static str], visitor: V, ) -> Result where - V: de::Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - serde::forward_to_deserialize_any! { - bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map unit identifier - ignored_any unit_struct tuple_struct tuple - } -} - -impl<'de, 'b> de::IntoDeserializer<'de, Error> for MapVisitor<'de, 'b> { - type Deserializer = MapVisitor<'de, 'b>; - - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -impl<'de, 'b> de::IntoDeserializer<'de, Error> for &'b mut Deserializer<'de> { - type Deserializer = Self; - - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -impl<'de> de::IntoDeserializer<'de, Error> for Value<'de> { - type Deserializer = ValueDeserializer<'de>; - - fn into_deserializer(self) -> Self::Deserializer { - ValueDeserializer::new(self) - } -} - -struct SpannedDeserializer<'de, T: de::IntoDeserializer<'de, Error>> { - phantom_data: PhantomData<&'de ()>, - start: Option, - end: Option, - value: Option, -} - -impl<'de, T> de::MapAccess<'de> for SpannedDeserializer<'de, T> -where - T: de::IntoDeserializer<'de, Error>, -{ - type Error = Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Error> - where - K: de::DeserializeSeed<'de>, - { - if self.start.is_some() { - seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::START_FIELD)) - .map(Some) - } else if self.end.is_some() { - seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::END_FIELD)) - .map(Some) - } else if self.value.is_some() { - seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::VALUE_FIELD)) - .map(Some) - } else { - Ok(None) - } - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - if let Some(start) = self.start.take() { - seed.deserialize(start.into_deserializer()) - } else if let Some(end) = self.end.take() { - seed.deserialize(end.into_deserializer()) - } else if let Some(value) = self.value.take() { - seed.deserialize(value.into_deserializer()) - } else { - panic!("next_value_seed called before next_key_seed") - } - } -} - -struct DatetimeDeserializer<'a> { - visited: bool, - date: &'a str, -} - -impl<'de> de::MapAccess<'de> for DatetimeDeserializer<'de> { - type Error = Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Error> - where - K: de::DeserializeSeed<'de>, - { - if self.visited { - return Ok(None); - } - self.visited = true; - seed.deserialize(DatetimeFieldDeserializer).map(Some) - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - seed.deserialize(StrDeserializer::new(self.date.into())) - } -} - -struct DatetimeFieldDeserializer; - -impl<'de> de::Deserializer<'de> for DatetimeFieldDeserializer { - type Error = Error; - - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_borrowed_str(datetime::FIELD) - } - - serde::forward_to_deserialize_any! { - bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map struct option unit newtype_struct - ignored_any unit_struct tuple_struct tuple enum identifier - } -} - -struct DottedTableDeserializer<'a> { - name: Cow<'a, str>, - value: Value<'a>, -} - -impl<'de> de::EnumAccess<'de> for DottedTableDeserializer<'de> { - type Error = Error; - type Variant = TableEnumDeserializer<'de>; - - fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: de::DeserializeSeed<'de>, - { - let (name, value) = (self.name, self.value); - seed.deserialize(StrDeserializer::new(name)) - .map(|val| (val, TableEnumDeserializer { value })) - } -} - -struct InlineTableDeserializer<'a> { - values: vec::IntoIter>, - next_value: Option>, -} - -impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> { - type Error = Error; - - fn next_key_seed(&mut self, seed: K) -> Result, Error> - where - K: de::DeserializeSeed<'de>, - { - let (key, value) = match self.values.next() { - Some(pair) => pair, - None => return Ok(None), - }; - self.next_value = Some(value); - seed.deserialize(StrDeserializer::spanned(key)).map(Some) - } - - fn next_value_seed(&mut self, seed: V) -> Result - where - V: de::DeserializeSeed<'de>, - { - let value = self.next_value.take().expect("Unable to read table values"); - seed.deserialize(ValueDeserializer::new(value)) - } -} - -impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { - type Error = Error; - type Variant = TableEnumDeserializer<'de>; - - fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> - where - V: de::DeserializeSeed<'de>, - { - let (key, value) = match self.values.next() { - Some(pair) => pair, - None => { - 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", - }, - )); - } - }; - - seed.deserialize(StrDeserializer::new(key.1)) - .map(|val| (val, TableEnumDeserializer { value })) - } -} - -/// Deserializes table values into enum variants. -struct TableEnumDeserializer<'a> { - value: Value<'a>, -} - -impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> { - type Error = Error; - - fn unit_variant(self) -> Result<(), Self::Error> { - match self.value.e { - E::InlineTable(values) | E::DottedTable(values) => { - if values.is_empty() { - Ok(()) - } else { - Err(Error::from_kind( - Some(self.value.start), - ErrorKind::ExpectedEmptyTable, - )) - } - } - e => Err(Error::from_kind( - Some(self.value.start), - ErrorKind::Wanted { - expected: "table", - found: e.type_name(), - }, - )), - } - } - - fn newtype_variant_seed(self, seed: T) -> Result - where - T: de::DeserializeSeed<'de>, + V: serde::de::Visitor<'de>, { - seed.deserialize(ValueDeserializer::new(self.value)) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_struct(name, fields, visitor) + .map_err(Error::new) } - fn tuple_variant(self, len: usize, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - match self.value.e { - E::InlineTable(values) | E::DottedTable(values) => { - let tuple_values = values - .into_iter() - .enumerate() - .map(|(index, (key, value))| match key.1.parse::() { - Ok(key_index) if key_index == index => Ok(value), - Ok(_) | Err(_) => Err(Error::from_kind( - Some(key.0.start), - ErrorKind::ExpectedTupleIndex { - expected: index, - found: key.1.to_string(), - }, - )), - }) - // Fold all values into a `Vec`, or return the first error. - .fold(Ok(Vec::with_capacity(len)), |result, value_result| { - result.and_then(move |mut tuple_values| match value_result { - Ok(value) => { - tuple_values.push(value); - Ok(tuple_values) - } - // `Result` to `Result, Self::Error>` - Err(e) => Err(e), - }) - })?; - - if tuple_values.len() == len { - de::Deserializer::deserialize_seq( - ValueDeserializer::new(Value { - e: E::Array(tuple_values), - start: self.value.start, - end: self.value.end, - }), - visitor, - ) - } else { - Err(Error::from_kind( - Some(self.value.start), - ErrorKind::ExpectedTuple(len), - )) - } - } - e => Err(Error::from_kind( - Some(self.value.start), - ErrorKind::Wanted { - expected: "table", - found: e.type_name(), - }, - )), - } - } - - fn struct_variant( + // Called when the type to deserialize is an enum, as opposed to a field in the type. + fn deserialize_enum( self, - fields: &'static [&'static str], + name: &'static str, + variants: &'static [&'static str], visitor: V, - ) -> Result - where - V: de::Visitor<'de>, - { - de::Deserializer::deserialize_struct( - ValueDeserializer::new(self.value).with_struct_key_validation(), - "", // TODO: this should be the variant name - fields, - visitor, - ) - } -} - -impl<'a> Deserializer<'a> { - /// Creates a new deserializer which will be deserializing the string - /// provided. - pub fn new(input: &'a str) -> Deserializer<'a> { - Deserializer { - tokens: Tokenizer::new(input), - input, - require_newline_after_table: true, - allow_duplciate_after_longer_table: false, - } - } - - /// The `Deserializer::end` method should be called after a value has been - /// fully deserialized. This allows the `Deserializer` to validate that the - /// input stream is at the end or that it only has trailing - /// whitespace/comments. - pub fn end(&mut self) -> Result<(), Error> { - Ok(()) - } - - #[doc(hidden)] - #[deprecated(since = "0.5.10")] - pub fn set_require_newline_after_table(&mut self, require: bool) { - self.require_newline_after_table = require; - } - - #[doc(hidden)] - #[deprecated(since = "0.5.10")] - pub fn set_allow_duplicate_after_longer_table(&mut self, allow: bool) { - self.allow_duplciate_after_longer_table = allow; - } - - fn tables(&mut self) -> Result>, Error> { - let mut tables = Vec::new(); - let mut cur_table = Table { - at: 0, - header: Vec::new(), - values: None, - array: false, - }; - - while let Some(line) = self.line()? { - match line { - Line::Table { - at, - mut header, - array, - } => { - if !cur_table.header.is_empty() || cur_table.values.is_some() { - tables.push(cur_table); - } - cur_table = Table { - at, - header: Vec::new(), - values: Some(Vec::new()), - array, - }; - loop { - let part = header.next().map_err(|e| self.token_error(e)); - match part? { - Some(part) => cur_table.header.push(part), - None => break, - } - } - } - Line::KeyValue(key, value) => { - if cur_table.values.is_none() { - cur_table.values = Some(Vec::new()); - } - self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?; - } - } - } - if !cur_table.header.is_empty() || cur_table.values.is_some() { - tables.push(cur_table); - } - Ok(tables) - } - - fn line(&mut self) -> Result>, Error> { - loop { - self.eat_whitespace()?; - if self.eat_comment()? { - continue; - } - if self.eat(Token::Newline)? { - continue; - } - break; - } - - match self.peek()? { - Some((_, Token::LeftBracket)) => self.table_header().map(Some), - Some(_) => self.key_value().map(Some), - None => Ok(None), - } - } - - fn table_header(&mut self) -> Result, Error> { - let start = self.tokens.current(); - self.expect(Token::LeftBracket)?; - let array = self.eat(Token::LeftBracket)?; - let ret = Header::new(self.tokens.clone(), array, self.require_newline_after_table); - if self.require_newline_after_table { - self.tokens.skip_to_newline(); - } else { - loop { - match self.next()? { - Some((_, Token::RightBracket)) => { - if array { - self.eat(Token::RightBracket)?; - } - break; - } - Some((_, Token::Newline)) | None => break, - _ => {} - } - } - self.eat_whitespace()?; - } - Ok(Line::Table { - at: start, - header: ret, - array, - }) - } - - fn key_value(&mut self) -> Result, Error> { - let key = self.dotted_key()?; - self.eat_whitespace()?; - self.expect(Token::Equals)?; - self.eat_whitespace()?; - - let value = self.value()?; - self.eat_whitespace()?; - if !self.eat_comment()? { - self.eat_newline_or_eof()?; - } - - Ok(Line::KeyValue(key, value)) - } - - fn value(&mut self) -> Result, Error> { - let at = self.tokens.current(); - let value = match self.next()? { - Some((Span { start, end }, Token::String { val, .. })) => Value { - e: E::String(val), - start, - end, - }, - Some((Span { start, end }, Token::Keylike("true"))) => Value { - e: E::Boolean(true), - start, - end, - }, - Some((Span { start, end }, Token::Keylike("false"))) => Value { - e: E::Boolean(false), - start, - end, - }, - Some((span, Token::Keylike(key))) => self.parse_keylike(at, span, key)?, - Some((span, Token::Plus)) => self.number_leading_plus(span)?, - Some((Span { start, .. }, Token::LeftBrace)) => { - self.inline_table().map(|(Span { end, .. }, table)| Value { - e: E::InlineTable(table), - start, - end, - })? - } - Some((Span { start, .. }, Token::LeftBracket)) => { - self.array().map(|(Span { end, .. }, array)| Value { - e: E::Array(array), - start, - end, - })? - } - Some(token) => { - return Err(self.error( - at, - ErrorKind::Wanted { - expected: "a value", - found: token.1.describe(), - }, - )); - } - None => return Err(self.eof()), - }; - Ok(value) - } - - fn parse_keylike(&mut self, at: usize, span: Span, key: &'a str) -> Result, Error> { - if key == "inf" || key == "nan" { - return self.number_or_date(span, key); - } - - let first_char = key.chars().next().expect("key should not be empty here"); - match first_char { - '-' | '0'..='9' => self.number_or_date(span, key), - _ => Err(self.error(at, ErrorKind::UnquotedString)), - } - } - - fn number_or_date(&mut self, span: Span, s: &'a str) -> Result, Error> { - if s.contains('T') - || s.contains('t') - || (s.len() > 1 && s[1..].contains('-') && !s.contains("e-") && !s.contains("E-")) - { - self.datetime(span, s, false) - .map(|(Span { start, end }, d)| Value { - e: E::Datetime(d), - start, - end, - }) - } else if self.eat(Token::Colon)? { - self.datetime(span, s, true) - .map(|(Span { start, end }, d)| Value { - e: E::Datetime(d), - start, - end, - }) - } else { - self.number(span, s) - } - } - - /// Returns a string or table value type. - /// - /// Used to deserialize enums. Unit enums may be represented as a string or a table, all other - /// structures (tuple, newtype, struct) must be represented as a table. - fn string_or_table(&mut self) -> Result<(Value<'a>, Option>), Error> { - match self.peek()? { - Some((span, Token::LeftBracket)) => { - let tables = self.tables()?; - if tables.len() != 1 { - 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 - .into_iter() - .next() - .expect("Expected exactly one table"); - let header = table - .header - .last() - .expect("Expected at least one header value for table."); - - let start = table.at; - let end = table - .values - .as_ref() - .and_then(|values| values.last()) - .map(|&(_, ref val)| val.end) - .unwrap_or_else(|| header.1.len()); - Ok(( - Value { - e: E::DottedTable(table.values.unwrap_or_default()), - start, - end, - }, - Some(header.1.clone()), - )) - } - Some(_) => self.value().map(|val| (val, None)), - None => Err(self.eof()), - } - } - - fn number(&mut self, Span { start, end }: Span, s: &'a str) -> Result, Error> { - let to_integer = |f| Value { - e: E::Integer(f), - start, - end, - }; - if let Some(value) = s.strip_prefix("0x") { - self.integer(value, 16).map(to_integer) - } else if let Some(value) = s.strip_prefix("0o") { - self.integer(value, 8).map(to_integer) - } else if let Some(value) = s.strip_prefix("0b") { - self.integer(value, 2).map(to_integer) - } else if s.contains('e') || s.contains('E') { - self.float(s, None).map(|f| Value { - e: E::Float(f), - start, - end, - }) - } else if self.eat(Token::Period)? { - let at = self.tokens.current(); - match self.next()? { - Some((Span { start, end }, Token::Keylike(after))) => { - self.float(s, Some(after)).map(|f| Value { - e: E::Float(f), - start, - end, - }) - } - _ => Err(self.error(at, ErrorKind::NumberInvalid)), - } - } else if s == "inf" { - Ok(Value { - e: E::Float(f64::INFINITY), - start, - end, - }) - } else if s == "-inf" { - Ok(Value { - e: E::Float(f64::NEG_INFINITY), - start, - end, - }) - } else if s == "nan" { - Ok(Value { - e: E::Float(f64::NAN), - start, - end, - }) - } else if s == "-nan" { - Ok(Value { - e: E::Float(-f64::NAN), - start, - end, - }) - } else { - self.integer(s, 10).map(to_integer) - } - } - - fn number_leading_plus(&mut self, Span { start, .. }: Span) -> Result, Error> { - let start_token = self.tokens.current(); - match self.next()? { - Some((Span { end, .. }, Token::Keylike(s))) => self.number(Span { start, end }, s), - _ => Err(self.error(start_token, ErrorKind::NumberInvalid)), - } - } - - fn integer(&self, s: &'a str, radix: u32) -> Result { - let allow_sign = radix == 10; - let allow_leading_zeros = radix != 10; - let (prefix, suffix) = self.parse_integer(s, allow_sign, allow_leading_zeros, radix)?; - let start = self.tokens.substr_offset(s); - if !suffix.is_empty() { - return Err(self.error(start, ErrorKind::NumberInvalid)); - } - i64::from_str_radix(prefix.replace('_', "").trim_start_matches('+'), radix) - .map_err(|_e| self.error(start, ErrorKind::NumberInvalid)) - } - - fn parse_integer( - &self, - s: &'a str, - allow_sign: bool, - allow_leading_zeros: bool, - radix: u32, - ) -> Result<(&'a str, &'a str), Error> { - let start = self.tokens.substr_offset(s); - - let mut first = true; - let mut first_zero = false; - let mut underscore = false; - let mut end = s.len(); - for (i, c) in s.char_indices() { - let at = i + start; - if i == 0 && (c == '+' || c == '-') && allow_sign { - continue; - } - - if c == '0' && first { - first_zero = true; - } else if c.is_digit(radix) { - if !first && first_zero && !allow_leading_zeros { - return Err(self.error(at, ErrorKind::NumberInvalid)); - } - underscore = false; - } else if c == '_' && first { - return Err(self.error(at, ErrorKind::NumberInvalid)); - } else if c == '_' && !underscore { - underscore = true; - } else { - end = i; - break; - } - first = false; - } - if first || underscore { - return Err(self.error(start, ErrorKind::NumberInvalid)); - } - Ok((&s[..end], &s[end..])) - } - - fn float(&mut self, s: &'a str, after_decimal: Option<&'a str>) -> Result { - let (integral, mut suffix) = self.parse_integer(s, true, false, 10)?; - let start = self.tokens.substr_offset(integral); - - let mut fraction = None; - if let Some(after) = after_decimal { - if !suffix.is_empty() { - return Err(self.error(start, ErrorKind::NumberInvalid)); - } - let (a, b) = self.parse_integer(after, false, true, 10)?; - fraction = Some(a); - suffix = b; - } - - let mut exponent = None; - if suffix.starts_with('e') || suffix.starts_with('E') { - let (a, b) = if suffix.len() == 1 { - self.eat(Token::Plus)?; - match self.next()? { - Some((_, Token::Keylike(s))) => self.parse_integer(s, false, true, 10)?, - _ => return Err(self.error(start, ErrorKind::NumberInvalid)), - } - } else { - self.parse_integer(&suffix[1..], true, true, 10)? - }; - if !b.is_empty() { - return Err(self.error(start, ErrorKind::NumberInvalid)); - } - exponent = Some(a); - } else if !suffix.is_empty() { - return Err(self.error(start, ErrorKind::NumberInvalid)); - } - - let mut number = integral - .trim_start_matches('+') - .chars() - .filter(|c| *c != '_') - .collect::(); - if let Some(fraction) = fraction { - number.push('.'); - number.extend(fraction.chars().filter(|c| *c != '_')); - } - if let Some(exponent) = exponent { - number.push('E'); - number.extend(exponent.chars().filter(|c| *c != '_')); - } - number - .parse() - .map_err(|_e| self.error(start, ErrorKind::NumberInvalid)) - .and_then(|n: f64| { - if n.is_finite() { - Ok(n) - } else { - Err(self.error(start, ErrorKind::NumberInvalid)) - } - }) - } - - fn datetime( - &mut self, - mut span: Span, - date: &'a str, - colon_eaten: bool, - ) -> Result<(Span, &'a str), Error> { - let start = self.tokens.substr_offset(date); - - // Check for space separated date and time. - let mut lookahead = self.tokens.clone(); - if let Ok(Some((_, Token::Whitespace(" ")))) = lookahead.next() { - // Check if hour follows. - if let Ok(Some((_, Token::Keylike(_)))) = lookahead.next() { - self.next()?; // skip space - self.next()?; // skip keylike hour - } - } - - if colon_eaten || self.eat(Token::Colon)? { - // minutes - match self.next()? { - Some((_, Token::Keylike(_))) => {} - _ => return Err(self.error(start, ErrorKind::DateInvalid)), - } - // Seconds - self.expect(Token::Colon)?; - match self.next()? { - Some((Span { end, .. }, Token::Keylike(_))) => { - span.end = end; - } - _ => return Err(self.error(start, ErrorKind::DateInvalid)), - } - // Fractional seconds - if self.eat(Token::Period)? { - match self.next()? { - Some((Span { end, .. }, Token::Keylike(_))) => { - span.end = end; - } - _ => return Err(self.error(start, ErrorKind::DateInvalid)), - } - } - - // offset - if self.eat(Token::Plus)? { - match self.next()? { - Some((Span { end, .. }, Token::Keylike(_))) => { - span.end = end; - } - _ => return Err(self.error(start, ErrorKind::DateInvalid)), - } - } - if self.eat(Token::Colon)? { - match self.next()? { - Some((Span { end, .. }, Token::Keylike(_))) => { - span.end = end; - } - _ => return Err(self.error(start, ErrorKind::DateInvalid)), - } - } - } - - let end = self.tokens.current(); - Ok((span, &self.tokens.input()[start..end])) - } - - // TODO(#140): shouldn't buffer up this entire table in memory, it'd be - // great to defer parsing everything until later. - fn inline_table(&mut self) -> Result<(Span, Vec>), Error> { - let mut ret = Vec::new(); - self.eat_whitespace()?; - if let Some(span) = self.eat_spanned(Token::RightBrace)? { - return Ok((span, ret)); - } - loop { - let key = self.dotted_key()?; - self.eat_whitespace()?; - self.expect(Token::Equals)?; - self.eat_whitespace()?; - let value = self.value()?; - self.add_dotted_key(key, value, &mut ret)?; - - self.eat_whitespace()?; - if let Some(span) = self.eat_spanned(Token::RightBrace)? { - return Ok((span, ret)); - } - self.expect(Token::Comma)?; - self.eat_whitespace()?; - } - } - - // TODO(#140): shouldn't buffer up this entire array in memory, it'd be - // great to defer parsing everything until later. - fn array(&mut self) -> Result<(Span, Vec>), Error> { - let mut ret = Vec::new(); - - let intermediate = |me: &mut Deserializer<'_>| { - loop { - me.eat_whitespace()?; - if !me.eat(Token::Newline)? && !me.eat_comment()? { - break; - } - } - Ok(()) - }; - - loop { - intermediate(self)?; - if let Some(span) = self.eat_spanned(Token::RightBracket)? { - return Ok((span, ret)); - } - let value = self.value()?; - ret.push(value); - intermediate(self)?; - if !self.eat(Token::Comma)? { - break; - } - } - intermediate(self)?; - let span = self.expect_spanned(Token::RightBracket)?; - Ok((span, ret)) - } - - fn table_key(&mut self) -> Result<(Span, Cow<'a, str>), Error> { - self.tokens.table_key().map_err(|e| self.token_error(e)) - } - - fn dotted_key(&mut self) -> Result)>, Error> { - let mut result = vec![self.table_key()?]; - self.eat_whitespace()?; - while self.eat(Token::Period)? { - self.eat_whitespace()?; - result.push(self.table_key()?); - self.eat_whitespace()?; - } - Ok(result) - } - - /// Stores a value in the appropriate hierarchical structure positioned based on the dotted key. - /// - /// Given the following definition: `multi.part.key = "value"`, `multi` and `part` are - /// intermediate parts which are mapped to the relevant fields in the deserialized type's data - /// hierarchy. - /// - /// # Parameters - /// - /// * `key_parts`: Each segment of the dotted key, e.g. `part.one` maps to - /// `vec![Cow::Borrowed("part"), Cow::Borrowed("one")].` - /// * `value`: The parsed value. - /// * `values`: The `Vec` to store the value in. - fn add_dotted_key( - &self, - mut key_parts: Vec<(Span, Cow<'a, str>)>, - value: Value<'a>, - values: &mut Vec>, - ) -> Result<(), Error> { - let key = key_parts.remove(0); - if key_parts.is_empty() { - values.push((key, value)); - return Ok(()); - } - match values.iter_mut().find(|&&mut (ref k, _)| *k.1 == key.1) { - Some(&mut ( - _, - Value { - e: E::DottedTable(ref mut v), - .. - }, - )) => { - return self.add_dotted_key(key_parts, value, v); - } - Some(&mut (_, Value { start, .. })) => { - return Err(self.error(start, ErrorKind::DottedKeyInvalidType)); - } - None => {} - } - // The start/end value is somewhat misleading here. - let table_values = Value { - e: E::DottedTable(Vec::new()), - start: value.start, - end: value.end, - }; - values.push((key, table_values)); - let last_i = values.len() - 1; - if let ( - _, - Value { - e: E::DottedTable(ref mut v), - .. - }, - ) = values[last_i] - { - self.add_dotted_key(key_parts, value, v)?; - } - Ok(()) - } - - fn eat_whitespace(&mut self) -> Result<(), Error> { - self.tokens - .eat_whitespace() - .map_err(|e| self.token_error(e)) - } - - fn eat_comment(&mut self) -> Result { - self.tokens.eat_comment().map_err(|e| self.token_error(e)) - } - - fn eat_newline_or_eof(&mut self) -> Result<(), Error> { - self.tokens - .eat_newline_or_eof() - .map_err(|e| self.token_error(e)) - } - - fn eat(&mut self, expected: Token<'a>) -> Result { - self.tokens.eat(expected).map_err(|e| self.token_error(e)) - } - - fn eat_spanned(&mut self, expected: Token<'a>) -> Result, Error> { - self.tokens - .eat_spanned(expected) - .map_err(|e| self.token_error(e)) - } - - fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> { - self.tokens - .expect(expected) - .map_err(|e| self.token_error(e)) - } - - fn expect_spanned(&mut self, expected: Token<'a>) -> Result { - self.tokens - .expect_spanned(expected) - .map_err(|e| self.token_error(e)) - } - - fn next(&mut self) -> Result)>, Error> { - self.tokens.next().map_err(|e| self.token_error(e)) - } - - fn peek(&mut self) -> Result)>, Error> { - self.tokens.peek().map_err(|e| self.token_error(e)) - } - - fn eof(&self) -> Error { - self.error(self.input.len(), ErrorKind::UnexpectedEof) - } - - fn token_error(&self, error: TokenError) -> Error { - match error { - TokenError::InvalidCharInString(at, ch) => { - self.error(at, ErrorKind::InvalidCharInString(ch)) - } - TokenError::InvalidEscape(at, ch) => self.error(at, ErrorKind::InvalidEscape(ch)), - TokenError::InvalidEscapeValue(at, v) => { - self.error(at, ErrorKind::InvalidEscapeValue(v)) - } - TokenError::InvalidHexEscape(at, ch) => self.error(at, ErrorKind::InvalidHexEscape(ch)), - TokenError::NewlineInString(at) => self.error(at, ErrorKind::NewlineInString), - TokenError::Unexpected(at, ch) => self.error(at, ErrorKind::Unexpected(ch)), - TokenError::UnterminatedString(at) => self.error(at, ErrorKind::UnterminatedString), - TokenError::NewlineInTableKey(at) => self.error(at, ErrorKind::NewlineInTableKey), - TokenError::Wanted { - at, - expected, - found, - } => self.error(at, ErrorKind::Wanted { expected, found }), - TokenError::MultilineStringKey(at) => self.error(at, ErrorKind::MultilineStringKey), - } - } - - fn error(&self, at: usize, kind: ErrorKind) -> Error { - let mut err = Error::from_kind(Some(at), kind); - err.fix_linecol(|at| self.to_linecol(at)); - err - } - - /// Converts a byte offset from an error message to a (line, column) pair - /// - /// All indexes are 0-based. - fn to_linecol(&self, offset: usize) -> (usize, usize) { - let mut cur = 0; - // Use split_terminator instead of lines so that if there is a `\r`, - // it is included in the offset calculation. The `+1` values below - // account for the `\n`. - for (i, line) in self.input.split_terminator('\n').enumerate() { - if cur + line.len() + 1 > offset { - return (i, offset - cur); - } - cur += line.len() + 1; - } - (self.input.lines().count(), 0) - } -} - -impl Error { - /// Produces a (line, column) pair of the position of the error if available - /// - /// All indexes are 0-based. - pub fn line_col(&self) -> Option<(usize, usize)> { - self.inner.line.map(|line| (line, self.inner.col)) - } - - fn from_kind(at: Option, kind: ErrorKind) -> Error { - Error { - inner: Box::new(ErrorInner { - kind, - line: None, - col: 0, - at, - message: String::new(), - key: Vec::new(), - }), - } - } - - fn custom(at: Option, s: String) -> Error { - Error { - inner: Box::new(ErrorInner { - kind: ErrorKind::Custom, - line: None, - col: 0, - at, - message: s, - key: Vec::new(), - }), - } - } - - pub(crate) fn add_key_context(&mut self, key: &str) { - self.inner.key.insert(0, key.to_string()); - } - - fn fix_offset(&mut self, f: F) - where - F: FnOnce() -> Option, - { - // 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(&mut self, f: F) + ) -> Result where - F: FnOnce(usize) -> (usize, usize), + V: serde::de::Visitor<'de>, { - if let Some(at) = self.inner.at { - let (line, col) = f(at); - self.inner.line = Some(line); - self.inner.col = col; - } - } -} - -impl std::convert::From for std::io::Error { - fn from(e: Error) -> Self { - std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner.kind { - ErrorKind::UnexpectedEof => "unexpected eof encountered".fmt(f)?, - ErrorKind::InvalidCharInString(c) => write!( - f, - "invalid character in string: `{}`", - c.escape_default().collect::() - )?, - ErrorKind::InvalidEscape(c) => write!( - f, - "invalid escape character in string: `{}`", - c.escape_default().collect::() - )?, - ErrorKind::InvalidHexEscape(c) => write!( - f, - "invalid hex escape character in string: `{}`", - c.escape_default().collect::() - )?, - ErrorKind::InvalidEscapeValue(c) => write!(f, "invalid escape value: `{}`", c)?, - ErrorKind::NewlineInString => "newline in string found".fmt(f)?, - ErrorKind::Unexpected(ch) => write!( - f, - "unexpected character found: `{}`", - ch.escape_default().collect::() - )?, - ErrorKind::UnterminatedString => "unterminated string".fmt(f)?, - ErrorKind::NewlineInTableKey => "found newline in table key".fmt(f)?, - ErrorKind::Wanted { expected, found } => { - write!(f, "expected {}, found {}", expected, found)? - } - ErrorKind::NumberInvalid => "invalid number".fmt(f)?, - ErrorKind::DateInvalid => "invalid date".fmt(f)?, - ErrorKind::DuplicateTable(ref s) => { - write!(f, "redefinition of table `{}`", s)?; - } - ErrorKind::RedefineAsArray => "table redefined as array".fmt(f)?, - ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?, - ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?, - ErrorKind::Custom => self.inner.message.fmt(f)?, - ErrorKind::ExpectedTuple(l) => write!(f, "expected table with length {}", l)?, - ErrorKind::ExpectedTupleIndex { - expected, - ref found, - } => write!(f, "expected table key `{}`, but was `{}`", expected, found)?, - ErrorKind::ExpectedEmptyTable => "expected empty table".fmt(f)?, - ErrorKind::DottedKeyInvalidType => { - "dotted key attempted to extend non-table type".fmt(f)? - } - ErrorKind::UnexpectedKeys { - ref keys, - available, - } => write!( - f, - "unexpected keys in table: `{:?}`, available keys: `{:?}`", - keys, available - )?, - ErrorKind::UnquotedString => write!( - f, - "invalid TOML value, did you mean to use a quoted string?" - )?, - } - - if !self.inner.key.is_empty() { - write!(f, " for key `")?; - for (i, k) in self.inner.key.iter().enumerate() { - if i > 0 { - write!(f, ".")?; - } - write!(f, "{}", k)?; - } - write!(f, "`")?; - } - - if let Some(line) = self.inner.line { - write!(f, " at line {} column {}", line + 1, self.inner.col + 1)?; - } - - Ok(()) - } -} - -impl error::Error for Error {} - -impl de::Error for Error { - fn custom(msg: T) -> Error { - Error::custom(None, msg.to_string()) + let inner = self + .input + .parse::() + .map_err(Error::new)?; + inner + .deserialize_enum(name, variants, visitor) + .map_err(Error::new) } -} - -enum Line<'a> { - Table { - at: usize, - header: Header<'a>, - array: bool, - }, - KeyValue(Vec<(Span, Cow<'a, str>)>, Value<'a>), -} - -struct Header<'a> { - first: bool, - array: bool, - require_newline_after_table: bool, - tokens: Tokenizer<'a>, -} -impl<'a> Header<'a> { - fn new(tokens: Tokenizer<'a>, array: bool, require_newline_after_table: bool) -> Header<'a> { - Header { - first: true, - array, - tokens, - require_newline_after_table, - } - } - - fn next(&mut self) -> Result)>, TokenError> { - self.tokens.eat_whitespace()?; - - if self.first || self.tokens.eat(Token::Period)? { - self.first = false; - self.tokens.eat_whitespace()?; - self.tokens.table_key().map(Some) - } else { - self.tokens.expect(Token::RightBracket)?; - if self.array { - self.tokens.expect(Token::RightBracket)?; - } - - self.tokens.eat_whitespace()?; - if self.require_newline_after_table && !self.tokens.eat_comment()? { - self.tokens.eat_newline_or_eof()?; - } - Ok(None) - } - } -} - -#[derive(Debug)] -struct Value<'a> { - e: E<'a>, - start: usize, - end: usize, -} - -#[derive(Debug)] -enum E<'a> { - Integer(i64), - Float(f64), - Boolean(bool), - String(Cow<'a, str>), - Datetime(&'a str), - Array(Vec>), - InlineTable(Vec>), - DottedTable(Vec>), -} - -impl<'a> E<'a> { - fn type_name(&self) -> &'static str { - match *self { - E::String(..) => "string", - E::Integer(..) => "integer", - E::Float(..) => "float", - E::Boolean(..) => "boolean", - E::Datetime(..) => "datetime", - E::Array(..) => "array", - E::InlineTable(..) => "inline table", - E::DottedTable(..) => "dotted table", - } + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map unit + ignored_any unit_struct tuple_struct tuple identifier } } diff --git a/crates/toml/src/lib.rs b/crates/toml/src/lib.rs index 4dd87fd9..13cf5387 100644 --- a/crates/toml/src/lib.rs +++ b/crates/toml/src/lib.rs @@ -155,8 +155,7 @@ pub mod ser; pub use crate::ser::{to_string, to_string_pretty, to_vec, Serializer}; pub mod de; #[doc(no_inline)] -pub use crate::de::{from_slice, from_str, Deserializer}; -mod tokens; +pub use crate::de::{from_str, Deserializer, ValueDeserializer}; #[doc(hidden)] pub mod macros; diff --git a/crates/toml/src/ser.rs b/crates/toml/src/ser.rs index efe8116a..74bacb6b 100644 --- a/crates/toml/src/ser.rs +++ b/crates/toml/src/ser.rs @@ -1140,7 +1140,7 @@ impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> { match *self { SerializeTable::Datetime(ref mut ser) => { if key == datetime::FIELD { - value.serialize(DateStrEmitter(*ser))?; + value.serialize(DateStrEmitter(ser))?; } else { return Err(Error::DateInvalid); } diff --git a/crates/toml/src/tokens.rs b/crates/toml/src/tokens.rs deleted file mode 100644 index ea1e7ebd..00000000 --- a/crates/toml/src/tokens.rs +++ /dev/null @@ -1,742 +0,0 @@ -use std::borrow::Cow; -use std::char; -use std::str; -use std::string; -use std::string::String as StdString; - -use self::Token::*; - -/// A span, designating a range of bytes where a token is located. -#[derive(Eq, PartialEq, Debug, Clone, Copy)] -pub struct Span { - /// The start of the range. - pub start: usize, - /// The end of the range (exclusive). - pub end: usize, -} - -impl From for (usize, usize) { - fn from(Span { start, end }: Span) -> (usize, usize) { - (start, end) - } -} - -#[derive(Eq, PartialEq, Debug)] -pub enum Token<'a> { - Whitespace(&'a str), - Newline, - Comment(&'a str), - - Equals, - Period, - Comma, - Colon, - Plus, - LeftBrace, - RightBrace, - LeftBracket, - RightBracket, - - Keylike(&'a str), - String { - src: &'a str, - val: Cow<'a, str>, - multiline: bool, - }, -} - -#[derive(Eq, PartialEq, Debug)] -pub enum Error { - InvalidCharInString(usize, char), - InvalidEscape(usize, char), - InvalidHexEscape(usize, char), - InvalidEscapeValue(usize, u32), - NewlineInString(usize), - Unexpected(usize, char), - UnterminatedString(usize), - NewlineInTableKey(usize), - MultilineStringKey(usize), - Wanted { - at: usize, - expected: &'static str, - found: &'static str, - }, -} - -#[derive(Clone)] -pub struct Tokenizer<'a> { - input: &'a str, - chars: CrlfFold<'a>, -} - -#[derive(Clone)] -struct CrlfFold<'a> { - chars: str::CharIndices<'a>, -} - -#[derive(Debug)] -enum MaybeString { - NotEscaped(usize), - Owned(string::String), -} - -impl<'a> Tokenizer<'a> { - pub fn new(input: &'a str) -> Tokenizer<'a> { - let mut t = Tokenizer { - input, - chars: CrlfFold { - chars: input.char_indices(), - }, - }; - // Eat utf-8 BOM - t.eatc('\u{feff}'); - t - } - - pub fn next(&mut self) -> Result)>, Error> { - let (start, token) = match self.one() { - Some((start, '\n')) => (start, Newline), - Some((start, ' ')) => (start, self.whitespace_token(start)), - Some((start, '\t')) => (start, self.whitespace_token(start)), - Some((start, '#')) => (start, self.comment_token(start)), - Some((start, '=')) => (start, Equals), - Some((start, '.')) => (start, Period), - Some((start, ',')) => (start, Comma), - Some((start, ':')) => (start, Colon), - Some((start, '+')) => (start, Plus), - Some((start, '{')) => (start, LeftBrace), - Some((start, '}')) => (start, RightBrace), - Some((start, '[')) => (start, LeftBracket), - Some((start, ']')) => (start, RightBracket), - Some((start, '\'')) => { - return self - .literal_string(start) - .map(|t| Some((self.step_span(start), t))) - } - Some((start, '"')) => { - return self - .basic_string(start) - .map(|t| Some((self.step_span(start), t))) - } - Some((start, ch)) if is_keylike(ch) => (start, self.keylike(start)), - - Some((start, ch)) => return Err(Error::Unexpected(start, ch)), - None => return Ok(None), - }; - - let span = self.step_span(start); - Ok(Some((span, token))) - } - - pub fn peek(&mut self) -> Result)>, Error> { - self.clone().next() - } - - pub fn eat(&mut self, expected: Token<'a>) -> Result { - self.eat_spanned(expected).map(|s| s.is_some()) - } - - /// Eat a value, returning it's span if it was consumed. - pub fn eat_spanned(&mut self, expected: Token<'a>) -> Result, Error> { - let span = match self.peek()? { - Some((span, ref found)) if expected == *found => span, - Some(_) => return Ok(None), - None => return Ok(None), - }; - - drop(self.next()); - Ok(Some(span)) - } - - pub fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> { - // ignore span - let _ = self.expect_spanned(expected)?; - Ok(()) - } - - /// Expect the given token returning its span. - pub fn expect_spanned(&mut self, expected: Token<'a>) -> Result { - let current = self.current(); - match self.next()? { - Some((span, found)) => { - if expected == found { - Ok(span) - } else { - Err(Error::Wanted { - at: current, - expected: expected.describe(), - found: found.describe(), - }) - } - } - None => Err(Error::Wanted { - at: self.input.len(), - expected: expected.describe(), - found: "eof", - }), - } - } - - pub fn table_key(&mut self) -> Result<(Span, Cow<'a, str>), Error> { - let current = self.current(); - match self.next()? { - Some((span, Token::Keylike(k))) => Ok((span, k.into())), - Some(( - span, - Token::String { - src, - val, - multiline, - }, - )) => { - let offset = self.substr_offset(src); - if multiline { - return Err(Error::MultilineStringKey(offset)); - } - match src.find('\n') { - None => Ok((span, val)), - Some(i) => Err(Error::NewlineInTableKey(offset + i)), - } - } - Some((_, other)) => Err(Error::Wanted { - at: current, - expected: "a table key", - found: other.describe(), - }), - None => Err(Error::Wanted { - at: self.input.len(), - expected: "a table key", - found: "eof", - }), - } - } - - pub fn eat_whitespace(&mut self) -> Result<(), Error> { - while self.eatc(' ') || self.eatc('\t') { - // ... - } - Ok(()) - } - - pub fn eat_comment(&mut self) -> Result { - if !self.eatc('#') { - return Ok(false); - } - drop(self.comment_token(0)); - self.eat_newline_or_eof().map(|()| true) - } - - pub fn eat_newline_or_eof(&mut self) -> Result<(), Error> { - let current = self.current(); - match self.next()? { - None | Some((_, Token::Newline)) => Ok(()), - Some((_, other)) => Err(Error::Wanted { - at: current, - expected: "newline", - found: other.describe(), - }), - } - } - - pub fn skip_to_newline(&mut self) { - loop { - match self.one() { - Some((_, '\n')) | None => break, - _ => {} - } - } - } - - fn eatc(&mut self, ch: char) -> bool { - match self.chars.clone().next() { - Some((_, ch2)) if ch == ch2 => { - self.one(); - true - } - _ => false, - } - } - - pub fn current(&mut self) -> usize { - self.chars - .clone() - .next() - .map(|i| i.0) - .unwrap_or_else(|| self.input.len()) - } - - pub fn input(&self) -> &'a str { - self.input - } - - fn whitespace_token(&mut self, start: usize) -> Token<'a> { - while self.eatc(' ') || self.eatc('\t') { - // ... - } - Whitespace(&self.input[start..self.current()]) - } - - fn comment_token(&mut self, start: usize) -> Token<'a> { - while let Some((_, ch)) = self.chars.clone().next() { - if ch != '\t' && !('\u{20}'..='\u{10ffff}').contains(&ch) { - break; - } - self.one(); - } - Comment(&self.input[start..self.current()]) - } - - #[allow(clippy::type_complexity)] - fn read_string( - &mut self, - delim: char, - start: usize, - new_ch: &mut dyn FnMut( - &mut Tokenizer<'_>, - &mut MaybeString, - bool, - usize, - char, - ) -> Result<(), Error>, - ) -> Result, Error> { - let mut multiline = false; - if self.eatc(delim) { - if self.eatc(delim) { - multiline = true; - } else { - return Ok(String { - src: &self.input[start..start + 2], - val: Cow::Borrowed(""), - multiline: false, - }); - } - } - let mut val = MaybeString::NotEscaped(self.current()); - let mut n = 0; - 'outer: loop { - n += 1; - match self.one() { - Some((i, '\n')) => { - if multiline { - if self.input.as_bytes()[i] == b'\r' { - val.to_owned(&self.input[..i]); - } - if n == 1 { - val = MaybeString::NotEscaped(self.current()); - } else { - val.push('\n'); - } - continue; - } else { - return Err(Error::NewlineInString(i)); - } - } - Some((mut i, ch)) if ch == delim => { - if multiline { - if !self.eatc(delim) { - val.push(delim); - continue 'outer; - } - if !self.eatc(delim) { - val.push(delim); - val.push(delim); - continue 'outer; - } - if self.eatc(delim) { - val.push(delim); - i += 1; - } - if self.eatc(delim) { - val.push(delim); - i += 1; - } - } - return Ok(String { - src: &self.input[start..self.current()], - val: val.into_cow(&self.input[..i]), - multiline, - }); - } - Some((i, c)) => new_ch(self, &mut val, multiline, i, c)?, - None => return Err(Error::UnterminatedString(start)), - } - } - } - - fn literal_string(&mut self, start: usize) -> Result, Error> { - self.read_string('\'', start, &mut |_me, val, _multi, i, ch| { - if ch == '\u{09}' || (('\u{20}'..='\u{10ffff}').contains(&ch) && ch != '\u{7f}') { - val.push(ch); - Ok(()) - } else { - Err(Error::InvalidCharInString(i, ch)) - } - }) - } - - fn basic_string(&mut self, start: usize) -> Result, Error> { - self.read_string('"', start, &mut |me, val, multi, i, ch| match ch { - '\\' => { - val.to_owned(&me.input[..i]); - match me.chars.next() { - Some((_, '"')) => val.push('"'), - Some((_, '\\')) => val.push('\\'), - Some((_, 'b')) => val.push('\u{8}'), - Some((_, 'f')) => val.push('\u{c}'), - Some((_, 'n')) => val.push('\n'), - Some((_, 'r')) => val.push('\r'), - Some((_, 't')) => val.push('\t'), - Some((i, c @ 'u')) | Some((i, c @ 'U')) => { - let len = if c == 'u' { 4 } else { 8 }; - val.push(me.hex(start, i, len)?); - } - Some((i, c @ ' ')) | Some((i, c @ '\t')) | Some((i, c @ '\n')) if multi => { - if c != '\n' { - while let Some((_, ch)) = me.chars.clone().next() { - match ch { - ' ' | '\t' => { - me.chars.next(); - continue; - } - '\n' => { - me.chars.next(); - break; - } - _ => return Err(Error::InvalidEscape(i, c)), - } - } - } - while let Some((_, ch)) = me.chars.clone().next() { - match ch { - ' ' | '\t' | '\n' => { - me.chars.next(); - } - _ => break, - } - } - } - Some((i, c)) => return Err(Error::InvalidEscape(i, c)), - None => return Err(Error::UnterminatedString(start)), - } - Ok(()) - } - ch if ch == '\u{09}' || (('\u{20}'..='\u{10ffff}').contains(&ch) && ch != '\u{7f}') => { - val.push(ch); - Ok(()) - } - _ => Err(Error::InvalidCharInString(i, ch)), - }) - } - - fn hex(&mut self, start: usize, i: usize, len: usize) -> Result { - let mut buf = StdString::with_capacity(len); - for _ in 0..len { - match self.one() { - Some((_, ch)) if ch as u32 <= 0x7F && ch.is_ascii_hexdigit() => buf.push(ch), - Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)), - None => return Err(Error::UnterminatedString(start)), - } - } - let val = u32::from_str_radix(&buf, 16).unwrap(); - match char::from_u32(val) { - Some(ch) => Ok(ch), - None => Err(Error::InvalidEscapeValue(i, val)), - } - } - - fn keylike(&mut self, start: usize) -> Token<'a> { - while let Some((_, ch)) = self.peek_one() { - if !is_keylike(ch) { - break; - } - self.one(); - } - Keylike(&self.input[start..self.current()]) - } - - pub fn substr_offset(&self, s: &'a str) -> usize { - assert!(s.len() <= self.input.len()); - let a = self.input.as_ptr() as usize; - let b = s.as_ptr() as usize; - assert!(a <= b); - b - a - } - - /// Calculate the span of a single character. - fn step_span(&mut self, start: usize) -> Span { - let end = self - .peek_one() - .map(|t| t.0) - .unwrap_or_else(|| self.input.len()); - Span { start, end } - } - - /// Peek one char without consuming it. - fn peek_one(&mut self) -> Option<(usize, char)> { - self.chars.clone().next() - } - - /// Take one char. - pub fn one(&mut self) -> Option<(usize, char)> { - self.chars.next() - } -} - -impl<'a> Iterator for CrlfFold<'a> { - type Item = (usize, char); - - fn next(&mut self) -> Option<(usize, char)> { - self.chars.next().map(|(i, c)| { - if c == '\r' { - let mut attempt = self.chars.clone(); - if let Some((_, '\n')) = attempt.next() { - self.chars = attempt; - return (i, '\n'); - } - } - (i, c) - }) - } -} - -impl MaybeString { - fn push(&mut self, ch: char) { - match *self { - MaybeString::NotEscaped(..) => {} - MaybeString::Owned(ref mut s) => s.push(ch), - } - } - - #[allow(clippy::wrong_self_convention)] - fn to_owned(&mut self, input: &str) { - match *self { - MaybeString::NotEscaped(start) => { - *self = MaybeString::Owned(input[start..].to_owned()); - } - MaybeString::Owned(..) => {} - } - } - - fn into_cow(self, input: &str) -> Cow<'_, str> { - match self { - MaybeString::NotEscaped(start) => Cow::Borrowed(&input[start..]), - MaybeString::Owned(s) => Cow::Owned(s), - } - } -} - -fn is_keylike(ch: char) -> bool { - ('A'..='Z').contains(&ch) - || ('a'..='z').contains(&ch) - || ('0'..='9').contains(&ch) - || ch == '-' - || ch == '_' -} - -impl<'a> Token<'a> { - pub fn describe(&self) -> &'static str { - match *self { - Token::Keylike(_) => "an identifier", - Token::Equals => "an equals", - Token::Period => "a period", - Token::Comment(_) => "a comment", - Token::Newline => "a newline", - Token::Whitespace(_) => "whitespace", - Token::Comma => "a comma", - Token::RightBrace => "a right brace", - Token::LeftBrace => "a left brace", - Token::RightBracket => "a right bracket", - Token::LeftBracket => "a left bracket", - Token::String { multiline, .. } => { - if multiline { - "a multiline string" - } else { - "a string" - } - } - Token::Colon => "a colon", - Token::Plus => "a plus", - } - } -} - -#[cfg(test)] -mod tests { - use super::{Error, Token, Tokenizer}; - use std::borrow::Cow; - - fn err(input: &str, err: Error) { - let mut t = Tokenizer::new(input); - let token = t.next().unwrap_err(); - assert_eq!(token, err); - assert!(t.next().unwrap().is_none()); - } - - #[test] - fn literal_strings() { - fn t(input: &str, val: &str, multiline: bool) { - let mut t = Tokenizer::new(input); - let (_, token) = t.next().unwrap().unwrap(); - assert_eq!( - token, - Token::String { - src: input, - val: Cow::Borrowed(val), - multiline, - } - ); - assert!(t.next().unwrap().is_none()); - } - - t("''", "", false); - t("''''''", "", true); - t("'''\n'''", "", true); - t("'a'", "a", false); - t("'\"a'", "\"a", false); - t("''''a'''", "'a", true); - t("'''\n'a\n'''", "'a\n", true); - t("'''a\n'a\r\n'''", "a\n'a\n", true); - } - - #[test] - fn basic_strings() { - fn t(input: &str, val: &str, multiline: bool) { - let mut t = Tokenizer::new(input); - let (_, token) = t.next().unwrap().unwrap(); - assert_eq!( - token, - Token::String { - src: input, - val: Cow::Borrowed(val), - multiline, - } - ); - assert!(t.next().unwrap().is_none()); - } - - t(r#""""#, "", false); - t(r#""""""""#, "", true); - t(r#""a""#, "a", false); - t(r#""""a""""#, "a", true); - t(r#""\t""#, "\t", false); - t(r#""\u0000""#, "\0", false); - t(r#""\U00000000""#, "\0", false); - t(r#""\U000A0000""#, "\u{A0000}", false); - t(r#""\\t""#, "\\t", false); - t("\"\t\"", "\t", false); - t("\"\"\"\n\t\"\"\"", "\t", true); - t("\"\"\"\\\n\"\"\"", "", true); - t( - "\"\"\"\\\n \t \t \\\r\n \t \n \t \r\n\"\"\"", - "", - true, - ); - t(r#""\r""#, "\r", false); - t(r#""\n""#, "\n", false); - t(r#""\b""#, "\u{8}", false); - t(r#""a\fa""#, "a\u{c}a", false); - t(r#""\"a""#, "\"a", false); - t("\"\"\"\na\"\"\"", "a", true); - t("\"\"\"\n\"\"\"", "", true); - t(r#""""a\"""b""""#, "a\"\"\"b", true); - err(r#""\a"#, Error::InvalidEscape(2, 'a')); - err("\"\\\n", Error::InvalidEscape(2, '\n')); - err("\"\\\r\n", Error::InvalidEscape(2, '\n')); - err("\"\\", Error::UnterminatedString(0)); - err("\"\u{0}", Error::InvalidCharInString(1, '\u{0}')); - err(r#""\U00""#, Error::InvalidHexEscape(5, '"')); - err(r#""\U00"#, Error::UnterminatedString(0)); - err(r#""\uD800"#, Error::InvalidEscapeValue(2, 0xd800)); - err(r#""\UFFFFFFFF"#, Error::InvalidEscapeValue(2, 0xffff_ffff)); - } - - #[test] - fn keylike() { - fn t(input: &str) { - let mut t = Tokenizer::new(input); - let (_, token) = t.next().unwrap().unwrap(); - assert_eq!(token, Token::Keylike(input)); - assert!(t.next().unwrap().is_none()); - } - t("foo"); - t("0bar"); - t("bar0"); - t("1234"); - t("a-b"); - t("a_B"); - t("-_-"); - t("___"); - } - - #[test] - fn all() { - fn t(input: &str, expected: &[((usize, usize), Token<'_>, &str)]) { - let mut tokens = Tokenizer::new(input); - let mut actual: Vec<((usize, usize), Token<'_>, &str)> = Vec::new(); - while let Some((span, token)) = tokens.next().unwrap() { - actual.push((span.into(), token, &input[span.start..span.end])); - } - for (a, b) in actual.iter().zip(expected) { - assert_eq!(a, b); - } - assert_eq!(actual.len(), expected.len()); - } - - t( - " a ", - &[ - ((0, 1), Token::Whitespace(" "), " "), - ((1, 2), Token::Keylike("a"), "a"), - ((2, 3), Token::Whitespace(" "), " "), - ], - ); - - t( - " a\t [[]] \t [] {} , . =\n# foo \r\n#foo \n ", - &[ - ((0, 1), Token::Whitespace(" "), " "), - ((1, 2), Token::Keylike("a"), "a"), - ((2, 4), Token::Whitespace("\t "), "\t "), - ((4, 5), Token::LeftBracket, "["), - ((5, 6), Token::LeftBracket, "["), - ((6, 7), Token::RightBracket, "]"), - ((7, 8), Token::RightBracket, "]"), - ((8, 11), Token::Whitespace(" \t "), " \t "), - ((11, 12), Token::LeftBracket, "["), - ((12, 13), Token::RightBracket, "]"), - ((13, 14), Token::Whitespace(" "), " "), - ((14, 15), Token::LeftBrace, "{"), - ((15, 16), Token::RightBrace, "}"), - ((16, 17), Token::Whitespace(" "), " "), - ((17, 18), Token::Comma, ","), - ((18, 19), Token::Whitespace(" "), " "), - ((19, 20), Token::Period, "."), - ((20, 21), Token::Whitespace(" "), " "), - ((21, 22), Token::Equals, "="), - ((22, 23), Token::Newline, "\n"), - ((23, 29), Token::Comment("# foo "), "# foo "), - ((29, 31), Token::Newline, "\r\n"), - ((31, 36), Token::Comment("#foo "), "#foo "), - ((36, 37), Token::Newline, "\n"), - ((37, 38), Token::Whitespace(" "), " "), - ], - ); - } - - #[test] - fn bare_cr_bad() { - err("\r", Error::Unexpected(0, '\r')); - err("'\n", Error::NewlineInString(1)); - err("'\u{0}", Error::InvalidCharInString(1, '\u{0}')); - err("'", Error::UnterminatedString(0)); - err("\u{0}", Error::Unexpected(0, '\u{0}')); - } - - #[test] - fn bad_comment() { - let mut t = Tokenizer::new("#\u{0}"); - t.next().unwrap().unwrap(); - assert_eq!(t.next(), Err(Error::Unexpected(1, '\u{0}'))); - assert!(t.next().unwrap().is_none()); - } -} diff --git a/crates/toml/src/value.rs b/crates/toml/src/value.rs index 6e4dbc94..9f904dc1 100644 --- a/crates/toml/src/value.rs +++ b/crates/toml/src/value.rs @@ -693,14 +693,11 @@ impl<'de> de::MapAccess<'de> for MapDeserializer { where T: de::DeserializeSeed<'de>, { - let (key, res) = match self.value.take() { + let (_key, res) = match self.value.take() { Some((key, value)) => (key, seed.deserialize(value)), None => return Err(de::Error::custom("value is missing")), }; - res.map_err(|mut error| { - error.add_key_context(&key); - error - }) + res } fn size_hint(&self) -> Option { diff --git a/crates/toml/tests/testsuite/backcompat.rs b/crates/toml/tests/testsuite/backcompat.rs deleted file mode 100644 index d169069e..00000000 --- a/crates/toml/tests/testsuite/backcompat.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::de::Deserialize; - -macro_rules! bad { - ($toml:expr, $msg:expr) => { - match $toml.parse::() { - Ok(s) => panic!("parsed to: {:#?}", s), - Err(e) => assert_eq!(e.to_string(), $msg), - } - }; -} - -#[test] -fn newlines_after_tables() { - let s = " - [a] foo = 1 - [[b]] foo = 1 - "; - bad!( - s, - "expected newline, found an identifier at line 2 column 13" - ); - - let mut d = toml::de::Deserializer::new(s); - d.set_require_newline_after_table(false); - let value = toml::Value::deserialize(&mut d).unwrap(); - assert_eq!(value["a"]["foo"].as_integer(), Some(1)); - assert_eq!(value["b"][0]["foo"].as_integer(), Some(1)); -} - -#[test] -fn allow_duplicate_after_longer() { - let s = " - [dependencies.openssl-sys] - version = 1 - - [dependencies] - libc = 1 - - [dependencies] - bitflags = 1 - "; - bad!( - s, - "redefinition of table `dependencies` for key `dependencies` at line 8 column 9" - ); - - let mut d = toml::de::Deserializer::new(s); - d.set_allow_duplicate_after_longer_table(true); - let value = toml::Value::deserialize(&mut d).unwrap(); - assert_eq!( - value["dependencies"]["openssl-sys"]["version"].as_integer(), - Some(1) - ); - assert_eq!(value["dependencies"]["libc"].as_integer(), Some(1)); - assert_eq!(value["dependencies"]["bitflags"].as_integer(), Some(1)); -} diff --git a/crates/toml/tests/testsuite/datetime.rs b/crates/toml/tests/testsuite/datetime.rs index 780f1f0d..ecb2bdd0 100644 --- a/crates/toml/tests/testsuite/datetime.rs +++ b/crates/toml/tests/testsuite/datetime.rs @@ -42,91 +42,214 @@ fn times() { fn bad_times() { bad!( "foo = 199-09-09", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 10 + | +1 | foo = 199-09-09 + | ^ +expected newline, `#` +" ); bad!( "foo = 199709-09", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 13 + | +1 | foo = 199709-09 + | ^ +expected newline, `#` +" ); bad!( "foo = 1997-9-09", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 12 + | +1 | foo = 1997-9-09 + | ^ +invalid date-time +" ); bad!( "foo = 1997-09-9", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 15 + | +1 | foo = 1997-09-9 + | ^ +invalid date-time +" ); bad!( "foo = 1997-09-0909:09:09", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 17 + | +1 | foo = 1997-09-0909:09:09 + | ^ +expected newline, `#` +" ); bad!( "foo = 1997-09-09T09:09:09.", - "invalid date at line 1 column 7" + "\ +TOML parse error at line 1, column 26 + | +1 | foo = 1997-09-09T09:09:09. + | ^ +expected newline, `#` +" ); bad!( "foo = T", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + r#"TOML parse error at line 1, column 7 + | +1 | foo = T + | ^ +invalid string +expected `"`, `'` +"# ); bad!( "foo = T.", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + r#"TOML parse error at line 1, column 7 + | +1 | foo = T. + | ^ +invalid string +expected `"`, `'` +"# ); bad!( "foo = TZ", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + r#"TOML parse error at line 1, column 7 + | +1 | foo = TZ + | ^ +invalid string +expected `"`, `'` +"# ); bad!( "foo = 1997-09-09T09:09:09.09+", - "invalid date at line 1 column 7" + r#"TOML parse error at line 1, column 30 + | +1 | foo = 1997-09-09T09:09:09.09+ + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09+09", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 32 + | +1 | foo = 1997-09-09T09:09:09.09+09 + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09+09:9", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 33 + | +1 | foo = 1997-09-09T09:09:09.09+09:9 + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09+0909", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 32 + | +1 | foo = 1997-09-09T09:09:09.09+0909 + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09-", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 30 + | +1 | foo = 1997-09-09T09:09:09.09- + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09-09", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 32 + | +1 | foo = 1997-09-09T09:09:09.09-09 + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09-09:9", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 33 + | +1 | foo = 1997-09-09T09:09:09.09-09:9 + | ^ +invalid time offset +"# ); bad!( "foo = 1997-09-09T09:09:09.09-0909", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 32 + | +1 | foo = 1997-09-09T09:09:09.09-0909 + | ^ +invalid time offset +"# ); bad!( "foo = 1997-00-09T09:09:09.09Z", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 12 + | +1 | foo = 1997-00-09T09:09:09.09Z + | ^ +invalid date-time +value is out of range +"# ); bad!( "foo = 1997-09-00T09:09:09.09Z", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 15 + | +1 | foo = 1997-09-00T09:09:09.09Z + | ^ +invalid date-time +value is out of range +"# ); bad!( "foo = 1997-09-09T30:09:09.09Z", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 17 + | +1 | foo = 1997-09-09T30:09:09.09Z + | ^ +expected newline, `#` +"# ); bad!( "foo = 1997-09-09T12:69:09.09Z", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 21 + | +1 | foo = 1997-09-09T12:69:09.09Z + | ^ +invalid date-time +value is out of range +"# ); bad!( "foo = 1997-09-09T12:09:69.09Z", - "failed to parse datetime for key `foo` at line 1 column 7" + r#"TOML parse error at line 1, column 24 + | +1 | foo = 1997-09-09T12:09:69.09Z + | ^ +invalid date-time +value is out of range +"# ); } diff --git a/crates/toml/tests/testsuite/de_errors.rs b/crates/toml/tests/testsuite/de_errors.rs index f8ff07b5..b3630bd4 100644 --- a/crates/toml/tests/testsuite/de_errors.rs +++ b/crates/toml/tests/testsuite/de_errors.rs @@ -83,7 +83,13 @@ fn custom_errors() { # ^ ", Parent, - "invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19" + "\ +TOML parse error at line 2, column 19 + | +2 | p_a = '' + | ^^ +invalid length 0, expected a non-empty string +" ); // Missing field in table. @@ -93,7 +99,13 @@ fn custom_errors() { # ^ ", Parent, - "missing field `p_b` at line 1 column 1" + "\ +TOML parse error at line 1, column 1 + | +1 | + | ^ +missing field `p_b` +" ); // Invalid type in p_b. @@ -104,7 +116,13 @@ fn custom_errors() { # ^ ", Parent, - "invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19" + "\ +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. @@ -117,7 +135,13 @@ fn custom_errors() { ] ", Parent, - "missing field `c_b` for key `p_b` at line 4 column 17" + "\ +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. @@ -130,7 +154,13 @@ fn custom_errors() { ] ", Parent, - "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b` at line 4 column 35" + "\ +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. @@ -144,7 +174,13 @@ fn custom_errors() { ] ", Parent, - "missing field `c_b` for key `p_b` at line 5 column 17" + "\ +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. @@ -159,7 +195,13 @@ fn custom_errors() { ] ", Parent, - "missing field `c_b` for key `p_b` at line 5 column 17" + "\ +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. @@ -174,11 +216,16 @@ fn custom_errors() { ] ", Parent, - "invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36" + "\ +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. - // FIXME: This location could be better. bad!( " p_a = 'a' @@ -191,7 +238,13 @@ fn custom_errors() { ] ", Parent, - "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17" + "\ +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. @@ -214,7 +267,13 @@ fn custom_errors() { c_b = 'bbbb' ", Parent, - "missing field `c_b` for key `p_b` at line 12 column 13" + "\ +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. @@ -233,11 +292,16 @@ fn custom_errors() { c_b = 'bbb' ", Parent, - "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19" + "\ +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. - // FIXME: This location is pretty off. bad!( " p_a = 'a' @@ -247,16 +311,22 @@ fn custom_errors() { [[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, - "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13" + "\ +TOML parse error at line 8, column 13 + | +8 | c_d = 'dd' # unknown field + | ^^^ +unknown field `c_d`, expected `c_a` or `c_b` +" ); } @@ -268,7 +338,13 @@ fn serde_derive_deserialize_errors() { # ^ ", Parent, - "missing field `p_b` at line 1 column 1" + "\ +TOML parse error at line 1, column 1 + | +1 | + | ^ +missing field `p_b` +" ); bad!( @@ -280,7 +356,13 @@ fn serde_derive_deserialize_errors() { ] ", Parent, - "missing field `c_b` for key `p_b` at line 4 column 17" + "\ +TOML parse error at line 4, column 17 + | +4 | {c_a = ''} + | ^^^^^^^^^^ +missing field `c_b` +" ); bad!( @@ -292,7 +374,13 @@ fn serde_derive_deserialize_errors() { ] ", Parent, - "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34" + "\ +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. @@ -305,7 +393,13 @@ fn serde_derive_deserialize_errors() { ] ", Parent, - "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17" + "\ +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!( @@ -317,7 +411,13 @@ fn serde_derive_deserialize_errors() { ] ", Parent, - "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34" + "\ +TOML parse error at line 4, column 34 + | +4 | {c_a = '', c_b = 1, c_d = ''}, + | ^ +invalid type: integer `1`, expected a string +" ); } @@ -331,7 +431,13 @@ fn error_handles_crlf() { a = 2\r\n\ ", toml::Value, - "duplicate key: `a` for key `t2` at line 3 column 1" + "\ +TOML parse error at line 5, column 1 + | +5 | a = 2 + | ^ +duplicate key `a` in table `t2` +" ); // Should be the same as above. @@ -343,6 +449,12 @@ fn error_handles_crlf() { a = 2\n\ ", toml::Value, - "duplicate key: `a` for key `t2` at line 3 column 1" + "\ +TOML parse error at line 5, column 1 + | +5 | a = 2 + | ^ +duplicate key `a` in table `t2` +" ); } diff --git a/crates/toml/tests/testsuite/enum_external_deserialize.rs b/crates/toml/tests/testsuite/enum_external_deserialize.rs index e3450c8f..e1a068cc 100644 --- a/crates/toml/tests/testsuite/enum_external_deserialize.rs +++ b/crates/toml/tests/testsuite/enum_external_deserialize.rs @@ -23,39 +23,98 @@ struct Multi { enums: Vec, } +fn value_from_str(s: &'_ str) -> Result +where + T: serde::de::DeserializeOwned, +{ + T::deserialize(toml::ValueDeserializer::new(s)) +} + #[test] fn invalid_variant_returns_error_with_good_message_string() { - let error = toml::from_str::("\"NonExistent\"").unwrap_err(); + let error = value_from_str::("\"NonExistent\"").unwrap_err(); + snapbox::assert_eq( + r#"unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, + error.to_string(), + ); + let error = toml::from_str::("val = \"NonExistent\"").unwrap_err(); snapbox::assert_eq( - "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`", + r#"TOML parse error at line 1, column 7 + | +1 | val = "NonExistent" + | ^^^^^^^^^^^^^ +unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, error.to_string(), ); } #[test] fn invalid_variant_returns_error_with_good_message_inline_table() { - let error = toml::from_str::("{ NonExistent = {} }").unwrap_err(); + let error = value_from_str::("{ NonExistent = {} }").unwrap_err(); snapbox::assert_eq( - "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`", + r#"unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, + error.to_string(), + ); + + let error = toml::from_str::("val = { NonExistent = {} }").unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 1, column 9 + | +1 | val = { NonExistent = {} } + | ^^^^^^^^^^^ +unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` +"#, error.to_string(), ); } #[test] fn extra_field_returns_expected_empty_table_error() { - let error = toml::from_str::("{ Plain = { extra_field = 404 } }").unwrap_err(); + let error = value_from_str::("{ Plain = { extra_field = 404 } }").unwrap_err(); + snapbox::assert_eq( + r#"expected empty table +"#, + error.to_string(), + ); - snapbox::assert_eq("expected empty table", error.to_string()); + let error = toml::from_str::("val = { Plain = { extra_field = 404 } }").unwrap_err(); + snapbox::assert_eq( + r#"TOML parse error at line 1, column 17 + | +1 | val = { Plain = { extra_field = 404 } } + | ^^^^^^^^^^^^^^^^^^^^^ +expected empty table +"#, + error.to_string(), + ); } #[test] fn extra_field_returns_expected_empty_table_error_struct_variant() { - let error = toml::from_str::("{ Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }") + let error = value_from_str::("{ Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }") .unwrap_err(); snapbox::assert_eq( - r#"unexpected keys in table: `["extra_0", "extra_1"]`, available keys: `["value"]`"#, + r#"unexpected keys in table: extra_0, extra_1, available keys: value +"#, + error.to_string(), + ); + + let error = + toml::from_str::("val = { Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }") + .unwrap_err(); + + snapbox::assert_eq( + r#"TOML parse error at line 1, column 33 + | +1 | val = { Struct = { value = 123, extra_0 = 0, extra_1 = 1 } } + | ^^^^^^^ +unexpected keys in table: extra_0, extra_1, available keys: value +"#, error.to_string(), ); } @@ -65,12 +124,19 @@ mod enum_unit { #[test] fn from_str() { - assert_eq!(TheEnum::Plain, toml::from_str("\"Plain\"").unwrap()); + assert_eq!(TheEnum::Plain, value_from_str("\"Plain\"").unwrap()); + + assert_eq!( + Val { + val: TheEnum::Plain + }, + toml::from_str("val = \"Plain\"").unwrap() + ); } #[test] fn from_inline_table() { - assert_eq!(TheEnum::Plain, toml::from_str("{ Plain = {} }").unwrap()); + assert_eq!(TheEnum::Plain, value_from_str("{ Plain = {} }").unwrap()); assert_eq!( Val { val: TheEnum::Plain @@ -80,7 +146,7 @@ mod enum_unit { } #[test] - fn from_dotted_table() { + fn from_std_table() { assert_eq!(TheEnum::Plain, toml::from_str("[Plain]\n").unwrap()); } } @@ -92,7 +158,7 @@ mod enum_tuple { fn from_inline_table() { assert_eq!( TheEnum::Tuple(-123, true), - toml::from_str("{ Tuple = { 0 = -123, 1 = true } }").unwrap() + value_from_str("{ Tuple = { 0 = -123, 1 = true } }").unwrap() ); assert_eq!( Val { @@ -103,7 +169,7 @@ mod enum_tuple { } #[test] - fn from_dotted_table() { + fn from_std_table() { assert_eq!( TheEnum::Tuple(-123, true), toml::from_str( @@ -124,7 +190,7 @@ mod enum_newtype { fn from_inline_table() { assert_eq!( TheEnum::NewType("value".to_string()), - toml::from_str(r#"{ NewType = "value" }"#).unwrap() + value_from_str(r#"{ NewType = "value" }"#).unwrap() ); assert_eq!( Val { @@ -135,8 +201,7 @@ mod enum_newtype { } #[test] - #[ignore = "Unimplemented: https://github.com/alexcrichton/toml-rs/pull/264#issuecomment-431707209"] - fn from_dotted_table() { + fn from_std_table() { assert_eq!( TheEnum::NewType("value".to_string()), toml::from_str(r#"NewType = "value""#).unwrap() @@ -162,7 +227,7 @@ mod enum_struct { fn from_inline_table() { assert_eq!( TheEnum::Struct { value: -123 }, - toml::from_str("{ Struct = { value = -123 } }").unwrap() + value_from_str("{ Struct = { value = -123 } }").unwrap() ); assert_eq!( Val { @@ -173,7 +238,7 @@ mod enum_struct { } #[test] - fn from_dotted_table() { + fn from_std_table() { assert_eq!( TheEnum::Struct { value: -123 }, toml::from_str( @@ -186,7 +251,7 @@ mod enum_struct { } #[test] - fn from_nested_dotted_table() { + fn from_nested_std_table() { assert_eq!( OuterStruct { inner: TheEnum::Struct { value: -123 } @@ -227,8 +292,7 @@ mod enum_array { } #[test] - #[ignore = "Unimplemented: https://github.com/alexcrichton/toml-rs/pull/264#issuecomment-431707209"] - fn from_dotted_table() { + fn from_std_table() { let toml_str = r#"[[enums]] Plain = {} diff --git a/crates/toml/tests/testsuite/invalid_misc.rs b/crates/toml/tests/testsuite/invalid_misc.rs index f5d67cf4..c7497343 100644 --- a/crates/toml/tests/testsuite/invalid_misc.rs +++ b/crates/toml/tests/testsuite/invalid_misc.rs @@ -9,25 +9,99 @@ macro_rules! bad { #[test] fn bad() { - bad!("a = 01", "invalid number at line 1 column 6"); - bad!("a = 1__1", "invalid number at line 1 column 5"); - bad!("a = 1_", "invalid number at line 1 column 5"); - bad!("''", "expected an equals, found eof at line 1 column 3"); - bad!("a = 9e99999", "invalid number at line 1 column 5"); + bad!( + "a = 01", + "\ +TOML parse error at line 1, column 6 + | +1 | a = 01 + | ^ +expected newline, `#` +" + ); + bad!( + "a = 1__1", + "\ +TOML parse error at line 1, column 7 + | +1 | a = 1__1 + | ^ +invalid integer +expected digit +" + ); + bad!( + "a = 1_", + "\ +TOML parse error at line 1, column 7 + | +1 | a = 1_ + | ^ +invalid integer +expected digit +" + ); + bad!( + "''", + "\ +TOML parse error at line 1, column 3 + | +1 | '' + | ^ +expected `.`, `=` +" + ); + bad!( + "a = 9e99999", + "\ +TOML parse error at line 1, column 5 + | +1 | a = 9e99999 + | ^ +invalid floating-point number +" + ); bad!( "a = \"\u{7f}\"", - "invalid character in string: `\\u{7f}` at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = \"\u{7f}\" + | ^ +invalid basic string +" ); bad!( "a = '\u{7f}'", - "invalid character in string: `\\u{7f}` at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = '\u{7f}' + | ^ +invalid literal string +" ); - bad!("a = -0x1", "invalid number at line 1 column 5"); + bad!( + "a = -0x1", + "\ +TOML parse error at line 1, column 7 + | +1 | a = -0x1 + | ^ +expected newline, `#` +" + ); bad!( "a = 0x-1", - "failed to parse datetime for key `a` at line 1 column 5" + "\ +TOML parse error at line 1, column 7 + | +1 | a = 0x-1 + | ^ +invalid hexadecimal integer +" ); // Dotted keys. @@ -35,15 +109,33 @@ fn bad() { "a.b.c = 1 a.b = 2 ", - "duplicate key: `b` for key `a` at line 1 column 9" + "\ +TOML parse error at line 2, column 10 + | +2 | a.b = 2 + | ^ +duplicate key `b` in document root +" ); bad!( "a = 1 a.b = 2", - "dotted key attempted to extend non-table type at line 1 column 5" + "\ +TOML parse error at line 2, column 10 + | +2 | a.b = 2 + | ^ +dotted key `a` attempted to extend non-table type (integer) +" ); bad!( "a = {k1 = 1, k1.name = \"joe\"}", - "dotted key attempted to extend non-table type at line 1 column 11" + "\ +TOML parse error at line 1, column 6 + | +1 | a = {k1 = 1, k1.name = \"joe\"} + | ^ +dotted key `k1` attempted to extend non-table type (integer) +" ); } diff --git a/crates/toml/tests/testsuite/main.rs b/crates/toml/tests/testsuite/main.rs index 6437cd19..649c2c95 100644 --- a/crates/toml/tests/testsuite/main.rs +++ b/crates/toml/tests/testsuite/main.rs @@ -1,6 +1,5 @@ #![recursion_limit = "256"] -mod backcompat; mod datetime; mod de_errors; mod display; diff --git a/crates/toml/tests/testsuite/parser.rs b/crates/toml/tests/testsuite/parser.rs index ce77b5cb..0d2324b8 100644 --- a/crates/toml/tests/testsuite/parser.rs +++ b/crates/toml/tests/testsuite/parser.rs @@ -184,30 +184,83 @@ name = "plantain" #[test] fn stray_cr() { - bad!("\r", "unexpected character found: `\\r` at line 1 column 1"); + bad!( + "\r", + "\ +TOML parse error at line 1, column 1 + | +1 | \r + | ^ + +" + ); bad!( "a = [ \r ]", - "unexpected character found: `\\r` at line 1 column 7" + "\ +TOML parse error at line 1, column 7 + | +1 | a = [ \r + ] + | ^ +invalid array +expected `]` +" ); bad!( "a = \"\"\"\r\"\"\"", - "invalid character in string: `\\r` at line 1 column 8" + "\ +TOML parse error at line 1, column 8 + | +1 | a = \"\"\"\r +\"\"\" + | ^ +invalid multiline basic string +" ); bad!( "a = \"\"\"\\ \r \"\"\"", - "invalid escape character in string: ` ` at line 1 column 9" + "\ +TOML parse error at line 1, column 10 + | +1 | a = \"\"\"\\ \r + \"\"\" + | ^ +invalid escape sequence +expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\\`, `\"` +" ); bad!( "a = '''\r'''", - "invalid character in string: `\\r` at line 1 column 8" + "\ +TOML parse error at line 1, column 8 + | +1 | a = '''\r +''' + | ^ +invalid multiline literal string +" ); bad!( "a = '\r'", - "invalid character in string: `\\r` at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = '\r +' + | ^ +invalid literal string +" ); bad!( "a = \"\r\"", - "invalid character in string: `\\r` at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = \"\r +\" + | ^ +invalid basic string +" ); } @@ -237,37 +290,187 @@ fn literal_eats_crlf() { #[test] fn string_no_newline() { - bad!("a = \"\n\"", "newline in string found at line 1 column 6"); - bad!("a = '\n'", "newline in string found at line 1 column 6"); + bad!( + "a = \"\n\"", + "\ +TOML parse error at line 1, column 6 + | +1 | a = \" + | ^ +invalid basic string +" + ); + bad!( + "a = '\n'", + "\ +TOML parse error at line 1, column 6 + | +1 | a = ' + | ^ +invalid literal string +" + ); } #[test] fn bad_leading_zeros() { - bad!("a = 00", "invalid number at line 1 column 6"); - bad!("a = -00", "invalid number at line 1 column 7"); - bad!("a = +00", "invalid number at line 1 column 7"); - bad!("a = 00.0", "invalid number at line 1 column 6"); - bad!("a = -00.0", "invalid number at line 1 column 7"); - bad!("a = +00.0", "invalid number at line 1 column 7"); + bad!( + "a = 00", + "\ +TOML parse error at line 1, column 6 + | +1 | a = 00 + | ^ +expected newline, `#` +" + ); + bad!( + "a = -00", + "\ +TOML parse error at line 1, column 7 + | +1 | a = -00 + | ^ +expected newline, `#` +" + ); + bad!( + "a = +00", + "\ +TOML parse error at line 1, column 7 + | +1 | a = +00 + | ^ +expected newline, `#` +" + ); + bad!( + "a = 00.0", + "\ +TOML parse error at line 1, column 6 + | +1 | a = 00.0 + | ^ +expected newline, `#` +" + ); + bad!( + "a = -00.0", + "\ +TOML parse error at line 1, column 7 + | +1 | a = -00.0 + | ^ +expected newline, `#` +" + ); + bad!( + "a = +00.0", + "\ +TOML parse error at line 1, column 7 + | +1 | a = +00.0 + | ^ +expected newline, `#` +" + ); bad!( "a = 9223372036854775808", - "invalid number at line 1 column 5" + "\ +TOML parse error at line 1, column 5 + | +1 | a = 9223372036854775808 + | ^ +number too large to fit in target type +" ); bad!( "a = -9223372036854775809", - "invalid number at line 1 column 5" + "\ +TOML parse error at line 1, column 5 + | +1 | a = -9223372036854775809 + | ^ +number too small to fit in target type +" ); } #[test] fn bad_floats() { - bad!("a = 0.", "invalid number at line 1 column 7"); - bad!("a = 0.e", "invalid number at line 1 column 7"); - bad!("a = 0.E", "invalid number at line 1 column 7"); - bad!("a = 0.0E", "invalid number at line 1 column 5"); - bad!("a = 0.0e", "invalid number at line 1 column 5"); - bad!("a = 0.0e-", "invalid number at line 1 column 9"); - bad!("a = 0.0e+", "invalid number at line 1 column 5"); + bad!( + "a = 0.", + "\ +TOML parse error at line 1, column 7 + | +1 | a = 0. + | ^ +invalid floating-point number +expected digit +" + ); + bad!( + "a = 0.e", + "\ +TOML parse error at line 1, column 7 + | +1 | a = 0.e + | ^ +invalid floating-point number +expected digit +" + ); + bad!( + "a = 0.E", + "\ +TOML parse error at line 1, column 7 + | +1 | a = 0.E + | ^ +invalid floating-point number +expected digit +" + ); + bad!( + "a = 0.0E", + "\ +TOML parse error at line 1, column 9 + | +1 | a = 0.0E + | ^ +invalid floating-point number +" + ); + bad!( + "a = 0.0e", + "\ +TOML parse error at line 1, column 9 + | +1 | a = 0.0e + | ^ +invalid floating-point number +" + ); + bad!( + "a = 0.0e-", + "\ +TOML parse error at line 1, column 10 + | +1 | a = 0.0e- + | ^ +invalid floating-point number +" + ); + bad!( + "a = 0.0e+", + "\ +TOML parse error at line 1, column 10 + | +1 | a = 0.0e+ + | ^ +invalid floating-point number +" + ); } #[test] @@ -330,44 +533,114 @@ fn bare_key_names() { fn bad_keys() { bad!( "key\n=3", - "expected an equals, found a newline at line 1 column 4" + "\ +TOML parse error at line 1, column 4 + | +1 | key + | ^ +expected `.`, `=` +" ); bad!( "key=\n3", - "expected a value, found a newline at line 1 column 5" + "\ +TOML parse error at line 1, column 5 + | +1 | key= + | ^ +invalid string +expected `\"`, `'` +" ); bad!( "key|=3", - "unexpected character found: `|` at line 1 column 4" + "\ +TOML parse error at line 1, column 4 + | +1 | key|=3 + | ^ +expected `.`, `=` +" ); bad!( "=3", - "expected a table key, found an equals at line 1 column 1" + "\ +TOML parse error at line 1, column 1 + | +1 | =3 + | ^ +invalid key +" ); bad!( "\"\"|=3", - "unexpected character found: `|` at line 1 column 3" + "\ +TOML parse error at line 1, column 3 + | +1 | \"\"|=3 + | ^ +expected `.`, `=` +" + ); + bad!( + "\"\n\"|=3", + "\ +TOML parse error at line 1, column 2 + | +1 | \" + | ^ +invalid basic string +" ); - bad!("\"\n\"|=3", "newline in string found at line 1 column 2"); bad!( "\"\r\"|=3", - "invalid character in string: `\\r` at line 1 column 2" + "\ +TOML parse error at line 1, column 2 + | +1 | \"\r\"|=3 + | ^ +invalid basic string +" ); bad!( "''''''=3", - "multiline strings are not allowed for key at line 1 column 1" + "\ +TOML parse error at line 1, column 3 + | +1 | ''''''=3 + | ^ +expected `.`, `=` +" ); bad!( "\"\"\"\"\"\"=3", - "multiline strings are not allowed for key at line 1 column 1" + "\ +TOML parse error at line 1, column 3 + | +1 | \"\"\"\"\"\"=3 + | ^ +expected `.`, `=` +" ); bad!( "'''key'''=3", - "multiline strings are not allowed for key at line 1 column 1" + "\ +TOML parse error at line 1, column 3 + | +1 | '''key'''=3 + | ^ +expected `.`, `=` +" ); bad!( "\"\"\"key\"\"\"=3", - "multiline strings are not allowed for key at line 1 column 1" + "\ +TOML parse error at line 1, column 3 + | +1 | \"\"\"key\"\"\"=3 + | ^ +expected `.`, `=` +" ); } @@ -375,38 +648,140 @@ fn bad_keys() { fn bad_table_names() { bad!( "[]", - "expected a table key, found a right bracket at line 1 column 2" + "\ +TOML parse error at line 1, column 2 + | +1 | [] + | ^ +invalid key +" ); bad!( "[.]", - "expected a table key, found a period at line 1 column 2" + "\ +TOML parse error at line 1, column 2 + | +1 | [.] + | ^ +invalid key +" ); bad!( "[a.]", - "expected a table key, found a right bracket at line 1 column 4" + "\ +TOML parse error at line 1, column 3 + | +1 | [a.] + | ^ +invalid table header +expected `.`, `]` +" + ); + bad!( + "[!]", + "\ +TOML parse error at line 1, column 2 + | +1 | [!] + | ^ +invalid key +" + ); + bad!( + "[\"\n\"]", + "\ +TOML parse error at line 1, column 3 + | +1 | [\" + | ^ +invalid basic string +" ); - bad!("[!]", "unexpected character found: `!` at line 1 column 2"); - bad!("[\"\n\"]", "newline in string found at line 1 column 3"); bad!( "[a.b]\n[a.\"b\"]", - "redefinition of table `a.b` for key `a.b` at line 2 column 1" + "\ +TOML parse error at line 2, column 1 + | +2 | [a.\"b\"] + | ^ +invalid table header +duplicate key `b` in table `a` +" + ); + bad!( + "[']", + "\ +TOML parse error at line 1, column 4 + | +1 | ['] + | ^ +invalid literal string +" + ); + bad!( + "[''']", + "\ +TOML parse error at line 1, column 4 + | +1 | ['''] + | ^ +invalid table header +expected `.`, `]` +" ); - bad!("[']", "unterminated string at line 1 column 2"); - bad!("[''']", "unterminated string at line 1 column 2"); bad!( "['''''']", - "multiline strings are not allowed for key at line 1 column 2" + "\ +TOML parse error at line 1, column 4 + | +1 | [''''''] + | ^ +invalid table header +expected `.`, `]` +" ); bad!( "['''foo''']", - "multiline strings are not allowed for key at line 1 column 2" + "\ +TOML parse error at line 1, column 4 + | +1 | ['''foo'''] + | ^ +invalid table header +expected `.`, `]` +" ); bad!( "[\"\"\"bar\"\"\"]", - "multiline strings are not allowed for key at line 1 column 2" + "\ +TOML parse error at line 1, column 4 + | +1 | [\"\"\"bar\"\"\"] + | ^ +invalid table header +expected `.`, `]` +" + ); + bad!( + "['\n']", + "\ +TOML parse error at line 1, column 3 + | +1 | [' + | ^ +invalid literal string +" + ); + bad!( + "['\r\n']", + "\ +TOML parse error at line 1, column 3 + | +1 | [' + | ^ +invalid literal string +" ); - bad!("['\n']", "newline in string found at line 1 column 3"); - bad!("['\r\n']", "newline in string found at line 1 column 3"); } #[test] @@ -431,7 +806,16 @@ fn table_names() { #[test] fn invalid_bare_numeral() { - bad!("4", "expected an equals, found eof at line 1 column 2"); + bad!( + "4", + "\ +TOML parse error at line 1, column 2 + | +1 | 4 + | ^ +expected `.`, `=` +" + ); } #[test] @@ -444,23 +828,57 @@ fn inline_tables() { bad!( "a = {a=1,}", - "expected a table key, found a right brace at line 1 column 10" + "\ +TOML parse error at line 1, column 9 + | +1 | a = {a=1,} + | ^ +invalid inline table +expected `}` +" ); bad!( "a = {,}", - "expected a table key, found a comma at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = {,} + | ^ +invalid inline table +expected `}` +" ); bad!( "a = {a=1,a=1}", - "duplicate key: `a` for key `a` at line 1 column 5" + "\ +TOML parse error at line 1, column 6 + | +1 | a = {a=1,a=1} + | ^ +duplicate key `a` +" ); bad!( "a = {\n}", - "expected a table key, found a newline at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = { + | ^ +invalid inline table +expected `}` +" ); bad!( "a = {", - "expected a table key, found eof at line 1 column 6" + "\ +TOML parse error at line 1, column 6 + | +1 | a = { + | ^ +invalid inline table +expected `}` +" ); "a = {a=[\n]}".parse::().unwrap(); @@ -487,20 +905,62 @@ fn number_underscores() { #[test] fn bad_underscores() { - bad!("foo = 0_", "invalid number at line 1 column 7"); - bad!("foo = 0__0", "invalid number at line 1 column 7"); + bad!( + "foo = 0_", + "\ +TOML parse error at line 1, column 8 + | +1 | foo = 0_ + | ^ +expected newline, `#` +" + ); + bad!( + "foo = 0__0", + "\ +TOML parse error at line 1, column 8 + | +1 | foo = 0__0 + | ^ +expected newline, `#` +" + ); bad!( "foo = __0", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + "\ +TOML parse error at line 1, column 7 + | +1 | foo = __0 + | ^ +invalid integer +expected leading digit +" + ); + bad!( + "foo = 1_0_", + "\ +TOML parse error at line 1, column 11 + | +1 | foo = 1_0_ + | ^ +invalid integer +expected digit +" ); - bad!("foo = 1_0_", "invalid number at line 1 column 7"); } #[test] fn bad_unicode_codepoint() { bad!( "foo = \"\\uD800\"", - "invalid escape value: `55296` at line 1 column 9" + "\ +TOML parse error at line 1, column 10 + | +1 | foo = \"\\uD800\" + | ^ +invalid unicode 4-digit hex code +value is out of range +" ); } @@ -508,14 +968,44 @@ fn bad_unicode_codepoint() { fn bad_strings() { bad!( "foo = \"\\uxx\"", - "invalid hex escape character in string: `x` at line 1 column 10" + "\ +TOML parse error at line 1, column 10 + | +1 | foo = \"\\uxx\" + | ^ +invalid unicode 4-digit hex code +" ); bad!( "foo = \"\\u\"", - "invalid hex escape character in string: `\\\"` at line 1 column 10" + "\ +TOML parse error at line 1, column 10 + | +1 | foo = \"\\u\" + | ^ +invalid unicode 4-digit hex code +" + ); + bad!( + "foo = \"\\", + "\ +TOML parse error at line 1, column 8 + | +1 | foo = \"\\ + | ^ +invalid basic string +" + ); + bad!( + "foo = '", + "\ +TOML parse error at line 1, column 8 + | +1 | foo = ' + | ^ +invalid literal string +" ); - bad!("foo = \"\\", "unterminated string at line 1 column 7"); - bad!("foo = '", "unterminated string at line 1 column 7"); } #[test] @@ -538,19 +1028,45 @@ fn booleans() { bad!( "foo = true2", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + "\ +TOML parse error at line 1, column 11 + | +1 | foo = true2 + | ^ +expected newline, `#` +" ); bad!( "foo = false2", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + "\ +TOML parse error at line 1, column 12 + | +1 | foo = false2 + | ^ +expected newline, `#` +" ); bad!( "foo = t1", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + "\ +TOML parse error at line 1, column 7 + | +1 | foo = t1 + | ^ +invalid string +expected `\"`, `'` +" ); bad!( "foo = f2", - "invalid TOML value, did you mean to use a quoted string? at line 1 column 7" + "\ +TOML parse error at line 1, column 7 + | +1 | foo = f2 + | ^ +invalid string +expected `\"`, `'` +" ); } @@ -562,28 +1078,56 @@ fn bad_nesting() { [[a]] b = 5 ", - "duplicate key: `a` at line 3 column 9" + "\ +TOML parse error at line 3, column 9 + | +3 | [[a]] + | ^ +invalid table header +duplicate key `a` in document root +" ); bad!( " a = 1 [a.b] ", - "duplicate key: `a` at line 3 column 9" + "\ +TOML parse error at line 3, column 9 + | +3 | [a.b] + | ^ +invalid table header +dotted key `a` attempted to extend non-table type (integer) +" ); bad!( " a = [] [a.b] ", - "duplicate key: `a` at line 3 column 9" + "\ +TOML parse error at line 3, column 9 + | +3 | [a.b] + | ^ +invalid table header +dotted key `a` attempted to extend non-table type (array) +" ); bad!( " a = [] [[a.b]] ", - "duplicate key: `a` at line 3 column 9" + "\ +TOML parse error at line 3, column 9 + | +3 | [[a.b]] + | ^ +invalid table header +dotted key `a` attempted to extend non-table type (array) +" ); bad!( " @@ -592,7 +1136,14 @@ fn bad_nesting() { [a.b] c = 2 ", - "duplicate key: `b` for key `a` at line 4 column 9" + "\ +TOML parse error at line 4, column 9 + | +4 | [a.b] + | ^ +invalid table header +duplicate key `b` in table `a` +" ); } @@ -606,7 +1157,14 @@ fn bad_table_redefine() { foo=\"bar\" [a] ", - "redefinition of table `a` for key `a` at line 6 column 9" + "\ +TOML parse error at line 6, column 9 + | +6 | [a] + | ^ +invalid table header +duplicate key `a` in document root +" ); bad!( " @@ -615,7 +1173,14 @@ fn bad_table_redefine() { b = { foo = \"bar\" } [a] ", - "redefinition of table `a` for key `a` at line 5 column 9" + "\ +TOML parse error at line 5, column 9 + | +5 | [a] + | ^ +invalid table header +duplicate key `a` in document root +" ); bad!( " @@ -623,7 +1188,14 @@ fn bad_table_redefine() { b = {} [a.b] ", - "duplicate key: `b` for key `a` at line 4 column 9" + "\ +TOML parse error at line 4, column 9 + | +4 | [a.b] + | ^ +invalid table header +duplicate key `b` in table `a` +" ); bad!( @@ -632,7 +1204,14 @@ fn bad_table_redefine() { b = {} [a] ", - "redefinition of table `a` for key `a` at line 4 column 9" + "\ +TOML parse error at line 4, column 9 + | +4 | [a] + | ^ +invalid table header +duplicate key `a` in document root +" ); } @@ -652,34 +1231,78 @@ 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` at line 1 column 7" + "\ +TOML parse error at line 1, column 26 + | +1 | foo = 2016-09-09T09:09:09.Z + | ^ +expected newline, `#` +" ); bad!( "foo = 2016-9-09T09:09:09Z", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 12 + | +1 | foo = 2016-9-09T09:09:09Z + | ^ +invalid date-time +" ); bad!( "foo = 2016-09-09T09:09:09+2:00", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 27 + | +1 | foo = 2016-09-09T09:09:09+2:00 + | ^ +invalid time offset +" ); bad!( "foo = 2016-09-09T09:09:09-2:00", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 27 + | +1 | foo = 2016-09-09T09:09:09-2:00 + | ^ +invalid time offset +" ); bad!( "foo = 2016-09-09T09:09:09Z-2:00", - "failed to parse datetime for key `foo` at line 1 column 7" + "\ +TOML parse error at line 1, column 27 + | +1 | foo = 2016-09-09T09:09:09Z-2:00 + | ^ +expected newline, `#` +" ); } #[test] fn require_newline_after_value() { - bad!("0=0r=false", "invalid number at line 1 column 3"); + bad!( + "0=0r=false", + "\ +TOML parse error at line 1, column 4 + | +1 | 0=0r=false + | ^ +expected newline, `#` +" + ); bad!( r#" 0=""o=""m=""r=""00="0"q="""0"""e="""0""" "#, - "expected newline, found an identifier at line 2 column 5" + r#"TOML parse error at line 2, column 5 + | +2 | 0=""o=""m=""r=""00="0"q="""0"""e="""0""" + | ^ +expected newline, `#` +"# ); bad!( r#" @@ -688,24 +1311,44 @@ fn require_newline_after_value() { 0="0"[[0000l0]] 0="0"l="0" "#, - "expected newline, found a left bracket at line 3 column 6" + r#"TOML parse error at line 3, column 6 + | +3 | 0="0"[[0000l0]] + | ^ +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, found an identifier at line 2 column 6" + r#"TOML parse error at line 2, column 6 + | +2 | 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 column 3" + r#"TOML parse error at line 2, column 4 + | +2 | 0=0r0=0r=false + | ^ +expected newline, `#` +"# ); bad!( r#" 0=0r0=0r=falsefal=false "#, - "invalid number at line 2 column 3" + r#"TOML parse error at line 2, column 4 + | +2 | 0=0r0=0r=falsefal=false + | ^ +expected newline, `#` +"# ); } diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs index 309f7e54..44fee218 100644 --- a/crates/toml/tests/testsuite/serde.rs +++ b/crates/toml/tests/testsuite/serde.rs @@ -272,8 +272,13 @@ fn type_errors() { Table(map! { bar: Value::String("a".to_string()) }), - "invalid type: string \"a\", expected isize for key `bar` at line 1 column 7", - "invalid type: string \"a\", expected isize for key `bar`" + r#"TOML parse error at line 1, column 7 + | +1 | bar = "a" + | ^^^ +invalid type: string "a", expected isize +"#, + "invalid type: string \"a\", expected isize\n" } #[derive(Deserialize)] @@ -289,8 +294,13 @@ fn type_errors() { bar: Value::String("a".to_string()) }) }), - "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`" + r#"TOML parse error at line 2, column 7 + | +2 | bar = "a" + | ^^^ +invalid type: string "a", expected isize +"#, + "invalid type: string \"a\", expected isize\n" } } @@ -304,8 +314,13 @@ fn missing_errors() { error! { Foo, Table(map! { }), - "missing field `bar`", - "missing field `bar`" + r#"TOML parse error at line 1, column 1 + | +1 | + | ^ +missing field `bar` +"#, + "missing field `bar`\n" } } diff --git a/crates/toml/tests/testsuite/spanned.rs b/crates/toml/tests/testsuite/spanned.rs index 708e21b0..430dd5ba 100644 --- a/crates/toml/tests/testsuite/spanned.rs +++ b/crates/toml/tests/testsuite/spanned.rs @@ -1,3 +1,6 @@ +#![allow(renamed_and_removed_lints)] +#![allow(clippy::blacklisted_name)] + use std::collections::HashMap; use std::fmt::Debug; @@ -34,9 +37,9 @@ fn test_spanned_field() { foo: T, } - fn good<'de, T>(s: &'de str, expected: &str, end: Option) + fn good(s: &str, expected: &str, end: Option) where - T: serde::Deserialize<'de> + Debug + PartialEq, + T: serde::de::DeserializeOwned + Debug + PartialEq, { let foo: Foo = toml::from_str(s).unwrap(); @@ -95,13 +98,7 @@ fn test_inner_spanned_table() { if zero { assert_eq!(foo.foo.span().start, 0); - // We'd actually have to assert equality with s.len() here, - // but the current implementation doesn't support that, - // and it's not possible with toml's data format to support it - // in the general case as spans aren't always well-defined. - // So this check mainly serves as a reminder that this test should - // be updated *if* one day there is support for emitting the actual span. - assert_eq!(foo.foo.span().end, 0); + assert_eq!(foo.foo.span().end, 73); } else { assert_eq!(foo.foo.span().start, s.find('{').unwrap()); assert_eq!(foo.foo.span().end, s.find('}').unwrap() + 1); @@ -113,7 +110,7 @@ fn test_inner_spanned_table() { } good( - " + "\ [foo] a = 'b' bar = 'baz' @@ -213,27 +210,7 @@ fn test_spanned_array() { foo: Vec, Spanned>>>, } - fn good(s: &str) { - let foo_list: Foo = toml::from_str(s).unwrap(); - - for foo in foo_list.foo.iter() { - assert_eq!(foo.span().start, 0); - // We'd actually have to assert equality with s.len() here, - // but the current implementation doesn't support that, - // and it's not possible with toml's data format to support it - // in the general case as spans aren't always well-defined. - // So this check mainly serves as a reminder that this test should - // be updated *if* one day there is support for emitting the actual span. - assert_eq!(foo.span().end, 0); - for (k, v) in foo.as_ref().iter() { - assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); - assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); - } - } - } - - good( - " + let toml = "\ [[foo]] a = 'b' bar = 'baz' @@ -244,6 +221,14 @@ fn test_spanned_array() { bar = 'baz' c = 'g' e = \"h\" - ", - ); + "; + let foo_list: Foo = toml::from_str(toml).unwrap(); + + for (foo, expected) in foo_list.foo.iter().zip([0..75, 84..159]) { + assert_eq!(foo.span(), expected); + for (k, v) in foo.as_ref().iter() { + assert_eq!(&toml[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&toml[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } } diff --git a/crates/toml_edit/src/de/mod.rs b/crates/toml_edit/src/de/mod.rs index b4b4b2e8..71d3f974 100644 --- a/crates/toml_edit/src/de/mod.rs +++ b/crates/toml_edit/src/de/mod.rs @@ -136,6 +136,8 @@ impl std::str::FromStr for Deserializer { } } +// Note: this is wrapped by `toml::de::Deserializer` and any trait methods +// implemented here need to be wrapped there impl<'de> serde::Deserializer<'de> for Deserializer { type Error = Error; diff --git a/crates/toml_edit/src/de/value.rs b/crates/toml_edit/src/de/value.rs index 5f553b97..ad2b40b5 100644 --- a/crates/toml_edit/src/de/value.rs +++ b/crates/toml_edit/src/de/value.rs @@ -23,6 +23,8 @@ impl ValueDeserializer { } } +// Note: this is wrapped by `toml::de::ValueDeserializer` and any trait methods +// implemented here need to be wrapped there impl<'de> serde::Deserializer<'de> for ValueDeserializer { type Error = Error;