Merge pull request #264 from azriel91/feature/225/externally-tagged-enums
Issue 255: Externally tagged enum deserialization
This commit is contained in:
commit
7845df9ff9
46
examples/enum_external.rs
Normal file
46
examples/enum_external.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//! An example showing off the usage of `Deserialize` to automatically decode
|
||||||
|
//! TOML into a Rust `struct`, with enums.
|
||||||
|
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
extern crate toml;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
/// This is what we're going to decode into.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Config {
|
||||||
|
plain: MyEnum,
|
||||||
|
plain_table: MyEnum,
|
||||||
|
tuple: MyEnum,
|
||||||
|
#[serde(rename = "struct")]
|
||||||
|
structv: MyEnum,
|
||||||
|
newtype: MyEnum,
|
||||||
|
my_enum: Vec<MyEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
enum MyEnum {
|
||||||
|
Plain,
|
||||||
|
Tuple(i64, bool),
|
||||||
|
NewType(String),
|
||||||
|
Struct { value: i64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let toml_str = r#"
|
||||||
|
plain = "Plain"
|
||||||
|
plain_table = { Plain = {} }
|
||||||
|
tuple = { Tuple = { 0 = 123, 1 = true } }
|
||||||
|
struct = { Struct = { value = 123 } }
|
||||||
|
newtype = { NewType = "value" }
|
||||||
|
my_enum = [
|
||||||
|
{ Plain = {} },
|
||||||
|
{ Tuple = { 0 = 123, 1 = true } },
|
||||||
|
{ NewType = "value" },
|
||||||
|
{ Struct = { value = 123 } }
|
||||||
|
]"#;
|
||||||
|
|
||||||
|
let decoded: Config = toml::from_str(toml_str).unwrap();
|
||||||
|
println!("{:#?}", decoded);
|
||||||
|
}
|
446
src/de.rs
446
src/de.rs
|
@ -159,12 +159,35 @@ enum ErrorKind {
|
||||||
/// type.
|
/// type.
|
||||||
Custom,
|
Custom,
|
||||||
|
|
||||||
/// A struct was expected but something else was found
|
/// A tuple with a certain number of elements was expected but something
|
||||||
ExpectedString,
|
/// 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.
|
/// Dotted key attempted to extend something that is not a table.
|
||||||
DottedKeyInvalidType,
|
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],
|
||||||
|
},
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
__Nonexhaustive,
|
__Nonexhaustive,
|
||||||
}
|
}
|
||||||
|
@ -182,47 +205,8 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
|
||||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
|
||||||
where V: de::Visitor<'de>,
|
where V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
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()? {
|
let mut tables = self.tables()?;
|
||||||
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: at,
|
|
||||||
header: Vec::new(),
|
|
||||||
values: Some(Vec::new()),
|
|
||||||
array: 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
visitor.visit_map(MapVisitor {
|
visitor.visit_map(MapVisitor {
|
||||||
values: Vec::new().into_iter(),
|
values: Vec::new().into_iter(),
|
||||||
|
@ -237,6 +221,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the type to deserialize is an enum, as opposed to a field in the type.
|
||||||
fn deserialize_enum<V>(
|
fn deserialize_enum<V>(
|
||||||
self,
|
self,
|
||||||
_name: &'static str,
|
_name: &'static str,
|
||||||
|
@ -245,15 +230,34 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
|
||||||
) -> Result<V::Value, Error>
|
) -> Result<V::Value, Error>
|
||||||
where V: de::Visitor<'de>
|
where V: de::Visitor<'de>
|
||||||
{
|
{
|
||||||
if let Some(next) = self.next()? {
|
let (value, name) = self.string_or_table()?;
|
||||||
match next {
|
match value.e {
|
||||||
(_, Token::String { val, .. }) => {
|
E::String(val) => visitor.visit_enum(val.into_deserializer()),
|
||||||
visitor.visit_enum(val.into_deserializer())
|
E::InlineTable(values) => {
|
||||||
},
|
if values.len() != 1 {
|
||||||
_ => Err(Error::from_kind(ErrorKind::ExpectedString))
|
Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
}
|
expected: "exactly 1 element",
|
||||||
|
found: if values.is_empty() {
|
||||||
|
"zero elements"
|
||||||
} else {
|
} else {
|
||||||
Err(Error::from_kind(ErrorKind::UnexpectedEof))
|
"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: value,
|
||||||
|
}),
|
||||||
|
e @ _ => Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
|
expected: "string or table",
|
||||||
|
found: e.type_name(),
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,14 +501,21 @@ impl<'de> de::Deserializer<'de> for StrDeserializer<'de> {
|
||||||
|
|
||||||
struct ValueDeserializer<'a> {
|
struct ValueDeserializer<'a> {
|
||||||
value: Value<'a>,
|
value: Value<'a>,
|
||||||
|
validate_struct_keys: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ValueDeserializer<'a> {
|
impl<'a> ValueDeserializer<'a> {
|
||||||
fn new(value: Value<'a>) -> ValueDeserializer<'a> {
|
fn new(value: Value<'a>) -> ValueDeserializer<'a> {
|
||||||
ValueDeserializer {
|
ValueDeserializer {
|
||||||
value: value,
|
value: value,
|
||||||
|
validate_struct_keys: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_struct_key_validation(mut self) -> Self {
|
||||||
|
self.validate_struct_keys = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
|
impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
|
||||||
|
@ -553,6 +564,31 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
Some(key.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<Cow<'de, str>>>();
|
||||||
|
|
||||||
|
if !extra_fields.is_empty() {
|
||||||
|
return Err(Error::from_kind(ErrorKind::UnexpectedKeys {
|
||||||
|
keys: extra_fields.iter().map(|k| k.to_string()).collect::<Vec<_>>(),
|
||||||
|
available: fields,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if name == spanned::NAME && fields == &[spanned::START, spanned::END, spanned::VALUE] {
|
if name == spanned::NAME && fields == &[spanned::START, spanned::END, spanned::VALUE] {
|
||||||
let start = self.value.start;
|
let start = self.value.start;
|
||||||
let end = self.value.end;
|
let end = self.value.end;
|
||||||
|
@ -585,7 +621,27 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
|
||||||
{
|
{
|
||||||
match self.value.e {
|
match self.value.e {
|
||||||
E::String(val) => visitor.visit_enum(val.into_deserializer()),
|
E::String(val) => visitor.visit_enum(val.into_deserializer()),
|
||||||
_ => Err(Error::from_kind(ErrorKind::ExpectedString))
|
E::InlineTable(values) => {
|
||||||
|
if values.len() != 1 {
|
||||||
|
Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
|
expected: "exactly 1 element",
|
||||||
|
found: if values.is_empty() {
|
||||||
|
"zero elements"
|
||||||
|
} else {
|
||||||
|
"more than 1 element"
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
visitor.visit_enum(InlineTableDeserializer {
|
||||||
|
values: values.into_iter(),
|
||||||
|
next_value: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e @ _ => Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
|
expected: "string or inline table",
|
||||||
|
found: e.type_name(),
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -697,6 +753,25 @@ impl<'de> de::Deserializer<'de> for DatetimeFieldDeserializer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<V>(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: value }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InlineTableDeserializer<'a> {
|
struct InlineTableDeserializer<'a> {
|
||||||
values: vec::IntoIter<(Cow<'a, str>, Value<'a>)>,
|
values: vec::IntoIter<(Cow<'a, str>, Value<'a>)>,
|
||||||
next_value: Option<Value<'a>>,
|
next_value: Option<Value<'a>>,
|
||||||
|
@ -724,6 +799,124 @@ impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> {
|
||||||
|
type Error = Error;
|
||||||
|
type Variant = TableEnumDeserializer<'de>;
|
||||||
|
|
||||||
|
fn variant_seed<V>(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(ErrorKind::Wanted {
|
||||||
|
expected: "table with exactly 1 entry",
|
||||||
|
found: "empty table",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
seed.deserialize(StrDeserializer::new(key))
|
||||||
|
.map(|val| (val, TableEnumDeserializer { value: 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.len() == 0 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::from_kind(ErrorKind::ExpectedEmptyTable))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e @ _ => Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
|
expected: "table",
|
||||||
|
found: e.type_name(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
|
||||||
|
where
|
||||||
|
T: de::DeserializeSeed<'de>,
|
||||||
|
{
|
||||||
|
seed.deserialize(ValueDeserializer::new(self.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
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.parse::<usize>() {
|
||||||
|
Ok(key_index) if key_index == index => Ok(value),
|
||||||
|
Ok(_) | Err(_) => Err(Error::from_kind(ErrorKind::ExpectedTupleIndex {
|
||||||
|
expected: index,
|
||||||
|
found: key.to_string(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
// 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<de::Value, Self::Error>` to `Result<Vec<_>, 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(ErrorKind::ExpectedTuple(len)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e @ _ => Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
|
expected: "table",
|
||||||
|
found: e.type_name(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_variant<V>(
|
||||||
|
self,
|
||||||
|
fields: &'static [&'static str],
|
||||||
|
visitor: V,
|
||||||
|
) -> Result<V::Value, Self::Error>
|
||||||
|
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> {
|
impl<'a> Deserializer<'a> {
|
||||||
/// Creates a new deserializer which will be deserializing the string
|
/// Creates a new deserializer which will be deserializing the string
|
||||||
|
@ -754,6 +947,53 @@ impl<'a> Deserializer<'a> {
|
||||||
self.require_newline_after_table = require;
|
self.require_newline_after_table = require;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tables(&mut self) -> Result<Vec<Table<'a>>, 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: at,
|
||||||
|
header: Vec::new(),
|
||||||
|
values: Some(Vec::new()),
|
||||||
|
array: 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<Option<Line<'a>>, Error> {
|
fn line(&mut self) -> Result<Option<Line<'a>>, Error> {
|
||||||
loop {
|
loop {
|
||||||
self.eat_whitespace()?;
|
self.eat_whitespace()?;
|
||||||
|
@ -876,6 +1116,55 @@ impl<'a> Deserializer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<Cow<'a, str>>), Error> {
|
||||||
|
match self.peek()? {
|
||||||
|
Some((_, Token::LeftBracket)) => {
|
||||||
|
let tables = self.tables()?;
|
||||||
|
if tables.len() != 1 {
|
||||||
|
return Err(Error::from_kind(ErrorKind::Wanted {
|
||||||
|
expected: "exactly 1 table",
|
||||||
|
found: if tables.is_empty() {
|
||||||
|
"zero tables"
|
||||||
|
} else {
|
||||||
|
"more than 1 table"
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.len());
|
||||||
|
Ok((
|
||||||
|
Value {
|
||||||
|
e: E::DottedTable(table.values.unwrap_or_else(Vec::new)),
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
},
|
||||||
|
Some(header.clone()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(_) => self.value().map(|val| (val, None)),
|
||||||
|
None => Err(self.eof()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn number(&mut self, Span { start, end }: Span, s: &'a str) -> Result<Value<'a>, Error> {
|
fn number(&mut self, Span { start, end }: Span, s: &'a str) -> Result<Value<'a>, Error> {
|
||||||
let to_integer = |f| Value { e: E::Integer(f), start: start, end: end };
|
let to_integer = |f| Value { e: E::Integer(f), start: start, end: end };
|
||||||
if s.starts_with("0x") {
|
if s.starts_with("0x") {
|
||||||
|
@ -1170,6 +1459,18 @@ impl<'a> Deserializer<'a> {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stores a value in the appropriate hierachical 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(
|
fn add_dotted_key(
|
||||||
&self,
|
&self,
|
||||||
mut key_parts: Vec<Cow<'a, str>>,
|
mut key_parts: Vec<Cow<'a, str>>,
|
||||||
|
@ -1191,12 +1492,12 @@ impl<'a> Deserializer<'a> {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
// The start/end value is somewhat misleading here.
|
// The start/end value is somewhat misleading here.
|
||||||
let inline_table = Value {
|
let table_values = Value {
|
||||||
e: E::DottedTable(Vec::new()),
|
e: E::DottedTable(Vec::new()),
|
||||||
start: value.start,
|
start: value.start,
|
||||||
end: value.end,
|
end: value.end,
|
||||||
};
|
};
|
||||||
values.push((key, inline_table));
|
values.push((key, table_values));
|
||||||
let last_i = values.len() - 1;
|
let last_i = values.len() - 1;
|
||||||
if let (_, Value { e: E::DottedTable(ref mut v), .. }) = values[last_i] {
|
if let (_, Value { e: E::DottedTable(ref mut v), .. }) = values[last_i] {
|
||||||
self.add_dotted_key(key_parts, value, v)?;
|
self.add_dotted_key(key_parts, value, v)?;
|
||||||
|
@ -1384,8 +1685,23 @@ impl fmt::Display for Error {
|
||||||
ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?,
|
ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?,
|
||||||
ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?,
|
ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?,
|
||||||
ErrorKind::Custom => self.inner.message.fmt(f)?,
|
ErrorKind::Custom => self.inner.message.fmt(f)?,
|
||||||
ErrorKind::ExpectedString => "expected string".fmt(f)?,
|
ErrorKind::ExpectedTuple(l) => write!(f, "expected table with length {}", l)?,
|
||||||
ErrorKind::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?,
|
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::__Nonexhaustive => panic!(),
|
ErrorKind::__Nonexhaustive => panic!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1429,8 +1745,11 @@ impl error::Error for Error {
|
||||||
ErrorKind::EmptyTableKey => "empty table key found",
|
ErrorKind::EmptyTableKey => "empty table key found",
|
||||||
ErrorKind::MultilineStringKey => "invalid multiline string for key",
|
ErrorKind::MultilineStringKey => "invalid multiline string for key",
|
||||||
ErrorKind::Custom => "a custom error",
|
ErrorKind::Custom => "a custom error",
|
||||||
ErrorKind::ExpectedString => "expected string",
|
ErrorKind::ExpectedTuple(_) => "expected table length",
|
||||||
|
ErrorKind::ExpectedTupleIndex { .. } => "expected table key",
|
||||||
|
ErrorKind::ExpectedEmptyTable => "expected empty table",
|
||||||
ErrorKind::DottedKeyInvalidType => "dotted key invalid type",
|
ErrorKind::DottedKeyInvalidType => "dotted key invalid type",
|
||||||
|
ErrorKind::UnexpectedKeys { .. } => "unexpected keys in table",
|
||||||
ErrorKind::__Nonexhaustive => panic!(),
|
ErrorKind::__Nonexhaustive => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1509,6 +1828,21 @@ enum E<'a> {
|
||||||
DottedTable(Vec<(Cow<'a, str>, Value<'a>)>),
|
DottedTable(Vec<(Cow<'a, str>, Value<'a>)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Value<'a> {
|
impl<'a> Value<'a> {
|
||||||
fn same_type(&self, other: &Value<'a>) -> bool {
|
fn same_type(&self, other: &Value<'a>) -> bool {
|
||||||
match (&self.e, &other.e) {
|
match (&self.e, &other.e) {
|
||||||
|
|
238
tests/enum_external_deserialize.rs
Normal file
238
tests/enum_external_deserialize.rs
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate toml;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
enum TheEnum {
|
||||||
|
Plain,
|
||||||
|
Tuple(i64, bool),
|
||||||
|
NewType(String),
|
||||||
|
Struct { value: i64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
struct Val {
|
||||||
|
val: TheEnum,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
struct Multi {
|
||||||
|
enums: Vec<TheEnum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_variant_returns_error_with_good_message_string() {
|
||||||
|
let error = toml::from_str::<TheEnum>("\"NonExistent\"").unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_variant_returns_error_with_good_message_inline_table() {
|
||||||
|
let error = toml::from_str::<TheEnum>("{ NonExistent = {} }").unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extra_field_returns_expected_empty_table_error() {
|
||||||
|
let error = toml::from_str::<TheEnum>("{ Plain = { extra_field = 404 } }").unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(error.to_string(), "expected empty table");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extra_field_returns_expected_empty_table_error_struct_variant() {
|
||||||
|
let error = toml::from_str::<TheEnum>("{ Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }")
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
r#"unexpected keys in table: `["extra_0", "extra_1"]`, available keys: `["value"]`"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod enum_unit {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_str() {
|
||||||
|
assert_eq!(TheEnum::Plain, toml::from_str("\"Plain\"").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_inline_table() {
|
||||||
|
assert_eq!(TheEnum::Plain, toml::from_str("{ Plain = {} }").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
Val {
|
||||||
|
val: TheEnum::Plain
|
||||||
|
},
|
||||||
|
toml::from_str("val = { Plain = {} }").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_dotted_table() {
|
||||||
|
assert_eq!(TheEnum::Plain, toml::from_str("[Plain]\n").unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod enum_tuple {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_inline_table() {
|
||||||
|
assert_eq!(
|
||||||
|
TheEnum::Tuple(-123, true),
|
||||||
|
toml::from_str("{ Tuple = { 0 = -123, 1 = true } }").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Val {
|
||||||
|
val: TheEnum::Tuple(-123, true)
|
||||||
|
},
|
||||||
|
toml::from_str("val = { Tuple = { 0 = -123, 1 = true } }").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_dotted_table() {
|
||||||
|
assert_eq!(
|
||||||
|
TheEnum::Tuple(-123, true),
|
||||||
|
toml::from_str(
|
||||||
|
r#"[Tuple]
|
||||||
|
0 = -123
|
||||||
|
1 = true
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod enum_newtype {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_inline_table() {
|
||||||
|
assert_eq!(
|
||||||
|
TheEnum::NewType("value".to_string()),
|
||||||
|
toml::from_str(r#"{ NewType = "value" }"#).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Val {
|
||||||
|
val: TheEnum::NewType("value".to_string()),
|
||||||
|
},
|
||||||
|
toml::from_str(r#"val = { NewType = "value" }"#).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "Unimplemented: https://github.com/alexcrichton/toml-rs/pull/264#issuecomment-431707209"]
|
||||||
|
fn from_dotted_table() {
|
||||||
|
assert_eq!(
|
||||||
|
TheEnum::NewType("value".to_string()),
|
||||||
|
toml::from_str(r#"NewType = "value""#).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Val {
|
||||||
|
val: TheEnum::NewType("value".to_string()),
|
||||||
|
},
|
||||||
|
toml::from_str(
|
||||||
|
r#"[val]
|
||||||
|
NewType = "value"
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod enum_struct {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_inline_table() {
|
||||||
|
assert_eq!(
|
||||||
|
TheEnum::Struct { value: -123 },
|
||||||
|
toml::from_str("{ Struct = { value = -123 } }").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Val {
|
||||||
|
val: TheEnum::Struct { value: -123 }
|
||||||
|
},
|
||||||
|
toml::from_str("val = { Struct = { value = -123 } }").unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_dotted_table() {
|
||||||
|
assert_eq!(
|
||||||
|
TheEnum::Struct { value: -123 },
|
||||||
|
toml::from_str(
|
||||||
|
r#"[Struct]
|
||||||
|
value = -123
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod enum_array {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_inline_tables() {
|
||||||
|
let toml_str = r#"
|
||||||
|
enums = [
|
||||||
|
{ Plain = {} },
|
||||||
|
{ Tuple = { 0 = -123, 1 = true } },
|
||||||
|
{ NewType = "value" },
|
||||||
|
{ Struct = { value = -123 } }
|
||||||
|
]"#;
|
||||||
|
assert_eq!(
|
||||||
|
Multi {
|
||||||
|
enums: vec![
|
||||||
|
TheEnum::Plain,
|
||||||
|
TheEnum::Tuple(-123, true),
|
||||||
|
TheEnum::NewType("value".to_string()),
|
||||||
|
TheEnum::Struct { value: -123 },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
toml::from_str(toml_str).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "Unimplemented: https://github.com/alexcrichton/toml-rs/pull/264#issuecomment-431707209"]
|
||||||
|
fn from_dotted_table() {
|
||||||
|
let toml_str = r#"[[enums]]
|
||||||
|
Plain = {}
|
||||||
|
|
||||||
|
[[enums]]
|
||||||
|
Tuple = { 0 = -123, 1 = true }
|
||||||
|
|
||||||
|
[[enums]]
|
||||||
|
NewType = "value"
|
||||||
|
|
||||||
|
[[enums]]
|
||||||
|
Struct = { value = -123 }
|
||||||
|
"#;
|
||||||
|
assert_eq!(
|
||||||
|
Multi {
|
||||||
|
enums: vec![
|
||||||
|
TheEnum::Plain,
|
||||||
|
TheEnum::Tuple(-123, true),
|
||||||
|
TheEnum::NewType("value".to_string()),
|
||||||
|
TheEnum::Struct { value: -123 },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
toml::from_str(toml_str).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue