diff options
author | Eugeen Sablin <EugeenSablin@gmail.com> | 2018-11-10 19:23:16 +0300 |
---|---|---|
committer | Eugeen Sablin <EugeenSablin@gmail.com> | 2018-11-10 19:23:16 +0300 |
commit | 2cb768bddc715c32e740a067652e7200c8d344f3 (patch) | |
tree | 1ca5ccc3a5794def758a765da835fe9b8b73b1a4 | |
parent | 802f947fa2b5060b605aa39c0c7c201ede9a5ed8 (diff) |
support reading enums from config
-rw-r--r-- | src/de.rs | 146 | ||||
-rw-r--r-- | tests/Settings.toml | 13 | ||||
-rw-r--r-- | tests/errors.rs | 40 | ||||
-rw-r--r-- | tests/get.rs | 23 |
4 files changed, 218 insertions, 4 deletions
@@ -5,7 +5,7 @@ use std::borrow::Cow; use std::collections::hash_map::Drain; use std::collections::HashMap; use std::iter::Peekable; -use value::{Value, ValueKind, ValueWithKey}; +use value::{Value, ValueKind, ValueWithKey, Table}; // TODO: Use a macro or some other magic to reduce the code duplication here @@ -113,9 +113,22 @@ impl<'de> de::Deserializer<'de> for ValueWithKey<'de> { } } + fn deserialize_enum<V>( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> + where + V: de::Visitor<'de>, + { + // FIXME: find a way to extend_with_key + visitor.visit_enum(EnumAccess{ value: self.0, name: name, variants: variants }) + } + forward_to_deserialize_any! { char seq - bytes byte_buf map struct unit enum newtype_struct + bytes byte_buf map struct unit newtype_struct identifier ignored_any unit_struct tuple_struct tuple } } @@ -231,9 +244,21 @@ impl<'de> de::Deserializer<'de> for Value { visitor.visit_newtype_struct(self) } + fn deserialize_enum<V>( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> + where + V: de::Visitor<'de>, + { + visitor.visit_enum(EnumAccess{ value: self, name: name, variants: variants }) + } + forward_to_deserialize_any! { char seq - bytes byte_buf map struct unit enum + bytes byte_buf map struct unit identifier ignored_any unit_struct tuple_struct tuple } } @@ -334,6 +359,107 @@ impl<'de> de::MapAccess<'de> for MapAccess { } } +struct EnumAccess { + value: Value, + name: &'static str, + variants: &'static [&'static str], +} + +impl EnumAccess { + fn variant_deserializer(&self, name: &String) -> Result<StrDeserializer> { + self.variants + .iter() + .find(|&s| s.to_lowercase() == name.to_lowercase()) + .map(|&s| StrDeserializer(s)) + .ok_or(self.no_constructor_error(name)) + } + + fn table_deserializer(&self, table: &Table) -> Result<StrDeserializer> { + if table.len() == 1 { + self.variant_deserializer(table.iter().next().unwrap().0) + } else { + Err(self.structural_error()) + } + } + + fn no_constructor_error(&self, supposed_variant: &str) -> ConfigError { + ConfigError::Message(format!( + "enum {} does not have variant constructor {}", + self.name, supposed_variant + )) + } + + fn structural_error(&self) -> ConfigError { + ConfigError::Message(format!( + "value of enum {} should be represented by either string or table with exactly one key", + self.name + )) + } +} + +impl<'de> de::EnumAccess<'de> for EnumAccess { + type Error = ConfigError; + type Variant = Self; + + fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)> + where + V: de::DeserializeSeed<'de>, + { + let value = { + let deserializer = match self.value.kind { + ValueKind::String(ref s) => self.variant_deserializer(s), + ValueKind::Table(ref t) => self.table_deserializer(&t), + _ => Err(self.structural_error()), + }?; + seed.deserialize(deserializer)? + }; + + Ok((value, self)) + } +} + +impl<'de> de::VariantAccess<'de> for EnumAccess { + type Error = ConfigError; + + fn unit_variant(self) -> Result<()> { + Ok(()) + } + + fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value> + where + T: de::DeserializeSeed<'de>, + { + match self.value.kind { + ValueKind::Table(t) => seed.deserialize(t.into_iter().next().unwrap().1), + _ => unreachable!(), + } + } + + fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value> + where + V: de::Visitor<'de>, + { + match self.value.kind { + ValueKind::Table(t) => de::Deserializer::deserialize_seq(t.into_iter().next().unwrap().1, visitor), + _ => unreachable!(), + } + } + + fn struct_variant<V>( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> + where + V: de::Visitor<'de>, + { + match self.value.kind { + ValueKind::Table(t) => de::Deserializer::deserialize_map(t.into_iter().next().unwrap().1, visitor), + _ => unreachable!(), + } + } +} + impl<'de> de::Deserializer<'de> for Config { type Error = ConfigError; @@ -438,9 +564,21 @@ impl<'de> de::Deserializer<'de> for Config { } } + fn deserialize_enum<V>( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result<V::Value> + where + V: de::Visitor<'de>, + { + visitor.visit_enum(EnumAccess{ value: self.cache, name: name, variants: variants }) + } + forward_to_deserialize_any! { char seq - bytes byte_buf map struct unit enum newtype_struct + bytes byte_buf map struct unit newtype_struct identifier ignored_any unit_struct tuple_struct tuple } } diff --git a/tests/Settings.toml b/tests/Settings.toml index 14f881f..26e6d26 100644 --- a/tests/Settings.toml +++ b/tests/Settings.toml @@ -10,6 +10,19 @@ boolean_s_parse = "fals" arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +[diodes] +green = "off" + +[diodes.red] +brightness = 100 + +[diodes.blue] +blinking = [300, 700] + +[diodes.white.pattern] +name = "christmas" +inifinite = true + [[items]] name = "1" diff --git a/tests/errors.rs b/tests/errors.rs index 6bc674f..fa2816e 100644 --- a/tests/errors.rs +++ b/tests/errors.rs @@ -2,6 +2,9 @@ extern crate config; +#[macro_use] +extern crate serde_derive; + use config::*; fn make() -> Config { @@ -52,3 +55,40 @@ fn test_error_type_detached() { "invalid type: string \"fals\", expected a boolean".to_string() ); } + +#[test] +fn test_error_enum_de() { + #[derive(Debug, Deserialize, PartialEq)] + enum Diode { + Off, + Brightness(i32), + Blinking(i32, i32), + Pattern { name: String, inifinite: bool }, + } + + let on_v: Value = "on".into(); + let on_d = on_v.try_into::<Diode>(); + assert_eq!( + on_d.unwrap_err().to_string(), + "enum Diode does not have variant constructor on".to_string() + ); + + let array_v: Value = vec![100, 100].into(); + let array_d = array_v.try_into::<Diode>(); + assert_eq!( + array_d.unwrap_err().to_string(), + "value of enum Diode should be represented by either string or table with exactly one key" + ); + + + let confused_v: Value = + [("Brightness".to_string(), 100.into()), + ("Blinking".to_string(), vec![300, 700].into())] + .iter().cloned().collect::<std::collections::HashMap<String, Value>>().into(); + let confused_d = confused_v.try_into::<Diode>(); + assert_eq!( + confused_d.unwrap_err().to_string(), + "value of enum Diode should be represented by either string or table with exactly one key" + ); +} + diff --git a/tests/get.rs b/tests/get.rs index 73eeeaf..7e3be8a 100644 --- a/tests/get.rs +++ b/tests/get.rs @@ -202,3 +202,26 @@ fn test_struct_array() { assert_eq!(s.elements.len(), 10); assert_eq!(s.elements[3], "4".to_string()); } + +#[test] +fn test_enum() { + #[derive(Debug, Deserialize, PartialEq)] + enum Diode { + Off, + Brightness(i32), + Blinking(i32, i32), + Pattern { name: String, inifinite: bool }, + } + #[derive(Debug, Deserialize)] + struct Settings { + diodes: HashMap<String, Diode>, + } + + let c = make(); + let s: Settings = c.try_into().unwrap(); + + assert_eq!(s.diodes["green"], Diode::Off); + assert_eq!(s.diodes["red"], Diode::Brightness(100)); + assert_eq!(s.diodes["blue"], Diode::Blinking(300, 700)); + assert_eq!(s.diodes["white"], Diode::Pattern{name: "christmas".into(), inifinite: true,}); +} |