//! Deserializing TOML into Rust structures. //! //! This module contains all the Serde support for deserializing TOML documents //! 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;
usecrate::datetime; usecrate::spanned; usecrate::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. pubfn from_slice<'de, T>(bytes: &'de [u8]) -> Result<T, Error> 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 /// deserialize `T` from the document. /// /// # Examples /// /// ``` /// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Config { /// title: String, /// owner: Owner, /// } /// /// #[derive(Deserialize)] /// struct Owner { /// name: String, /// } /// /// let config: Config = toml::from_str(r#" /// title = 'TOML Example' /// /// [owner] /// name = 'Lisa' /// "#).unwrap(); /// /// assert_eq!(config.title, "TOML Example"); /// assert_eq!(config.owner.name, "Lisa"); /// ``` pubfn from_str<'de, T>(s: &'de str) -> Result<T, Error> where
T: de::Deserialize<'de>,
{ letmut d = Deserializer::new(s); let ret = T::deserialize(&mut d)?;
d.end()?;
Ok(ret)
}
/// Errors that can occur when deserializing a type. #[derive(Debug, PartialEq, Eq, Clone)] pubstruct Error {
inner: Box<ErrorInner>,
}
/// 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<String>, /// Keys that may be specified.
available: &'static [&'static str],
},
/// Unquoted string was found when quoted one was expected
UnquotedString,
}
impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error> where
V: de::Visitor<'de>,
{ letmut 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<V>( self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error> 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<V>( self,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error> where
V: de::Visitor<'de>,
{ if name == spanned::NAME && fields == [spanned::START, spanned::END, spanned::VALUE] { 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;
}
// 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<Cow<'de, str>>, Vec<usize>> { letmut res = HashMap::new(); for (i, table) in tables.iter().enumerate() { let header = table.header.iter().map(|v| v.1.clone()).collect::<Vec<_>>();
res.entry(header).or_insert_with(Vec::new).push(i);
}
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<Cow<'de, str>>, Vec<usize>> { letmut res = HashMap::new(); for (i, table) in tables.iter().enumerate() { let header = table.header.iter().map(|v| v.1.clone()).collect::<Vec<_>>(); for len in0..=header.len() {
res.entry(header[..len].to_owned())
.or_insert_with(Vec::new)
.push(i);
}
}
res
}
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 ifself.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::<Vec<_>>()
.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 = &mutself.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. ifself.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));
}
// `None` is interpreted as a missing field so be sure to implement `Some` // as a present field. fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error> where
V: de::Visitor<'de>,
{
visitor.visit_some(self)
}
fn deserialize_struct<V>( mutself,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error> where
V: de::Visitor<'de>,
{ if name == spanned::NAME
&& fields == [spanned::START, spanned::END, spanned::VALUE]
&& !(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<T>. 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)
}
fn deserialize_enum<V>( self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Error> where
V: de::Visitor<'de>,
{ ifself.tables.len() != 1 { return Err(Error::custom(
Some(self.cur), "enum table must contain exactly one table".into(),
));
} let table = &mutself.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,
},
})
}
// `None` is interpreted as a missing field so be sure to implement `Some` // as a present field. fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error> where
V: de::Visitor<'de>,
{
visitor.visit_some(self)
}
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)),
}
}
/// 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(&mutself) -> Result<(Value<'a>, Option<Cow<'a, str>>), Error> { matchself.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()),
}
}
letmut first = true; letmut first_zero = false; letmut underscore = false; letmut 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;
} elseif c.is_digit(radix) { if !first && first_zero && !allow_leading_zeros { return Err(self.error(at, ErrorKind::NumberInvalid));
}
underscore = false;
} elseif c == '_' && first { return Err(self.error(at, ErrorKind::NumberInvalid));
} elseif 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..]))
}
// TODO(#140): shouldn't buffer up this entire array in memory, it'd be // great to defer parsing everything until later. fn array(&mutself) -> Result<(Span, Vec<Value<'a>>), Error> { letmut 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)?; iflet 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))
}
/// 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<TablePair<'a>>,
) -> 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(refmut v),
..
},
)) => { returnself.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; iflet (
_,
Value {
e: E::DottedTable(refmut v),
..
},
) = values[last_i]
{ self.add_dotted_key(key_parts, value, v)?;
}
Ok(())
}
/// 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) { letmut 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) inself.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. pubfn line_col(&self) -> Option<(usize, usize)> { self.inner.line.map(|line| (line, self.inner.col))
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.