summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Leckey <leckey.ryan@gmail.com>2018-11-28 14:17:27 -0800
committerGitHub <noreply@github.com>2018-11-28 14:17:27 -0800
commit4d8a1d018fffe141b7ac7594239fded599befbc3 (patch)
tree1ca5ccc3a5794def758a765da835fe9b8b73b1a4
parent802f947fa2b5060b605aa39c0c7c201ede9a5ed8 (diff)
parent2cb768bddc715c32e740a067652e7200c8d344f3 (diff)
Merge pull request #85 from geniusisme/genius_isme/de_enum
support reading enums from config
-rw-r--r--src/de.rs146
-rw-r--r--tests/Settings.toml13
-rw-r--r--tests/errors.rs40
-rw-r--r--tests/get.rs23
4 files changed, 218 insertions, 4 deletions
diff --git a/src/de.rs b/src/de.rs
index 89d0c0c..e465d60 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -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,});
+}