diff --git a/examples/enum_external.rs b/examples/enum_external.rs new file mode 100644 index 0000000..1feff77 --- /dev/null +++ b/examples/enum_external.rs @@ -0,0 +1,40 @@ +//! 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, + // tuple: MyEnum, + #[serde(rename = "struct")] + structv: MyEnum, + my_enum: Vec, +} + +#[derive(Debug, Deserialize)] +enum MyEnum { + Plain, + Tuple(i64, bool), + Struct { value: i64 }, +} + +fn main() { + let toml_str = r#" + plain = "Plain" + # tuple = { 0 = 123, 1 = true } + struct = { Struct = { value = 123 } } + my_enum = [ + { Plain = {} }, + # { Tuple = { 0 = 123, 1 = true } }, + { Struct = { value = 123 } } + ]"#; + + let decoded: Config = toml::from_str(toml_str).unwrap(); + println!("{:#?}", decoded); +} diff --git a/src/de.rs b/src/de.rs index 491ae47..5de4f6a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -585,7 +585,27 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { { match self.value.e { E::String(val) => visitor.visit_enum(val.into_deserializer()), - _ => Err(Error::from_kind(ErrorKind::ExpectedString)) + E::InlineTable(values) | E::DottedTable(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 table", + found: e.type_name(), + })), } } @@ -724,6 +744,70 @@ impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> { } } +impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> { + type Error = Error; + type Variant = Self; + + 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(ErrorKind::Wanted { + expected: "table with exactly 1 entry", + found: "empty map", + })) + } + }; + self.next_value = Some(value); + + seed.deserialize(StrDeserializer::new(key)) + .map(|val| (val, self)) + } +} + +impl<'de> de::VariantAccess<'de> for InlineTableDeserializer<'de> { + type Error = Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + // TODO: Error handling if there are entries + Ok(()) + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + seed.deserialize(ValueDeserializer::new( + self.next_value.expect("Expected value"), + )) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + unimplemented!() + } + + fn struct_variant( + self, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + de::Deserializer::deserialize_struct( + ValueDeserializer::new(self.next_value.expect("Expected value")), + "", // TODO: this should be the variant name + fields, + visitor, + ) + } +} impl<'a> Deserializer<'a> { /// Creates a new deserializer which will be deserializing the string @@ -1510,6 +1594,21 @@ enum E<'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> { fn same_type(&self, other: &Value<'a>) -> bool { match (&self.e, &other.e) {