Skip to content

Commit

Permalink
Merge pull request #85 from geniusisme/genius_isme/de_enum
Browse files Browse the repository at this point in the history
support reading enums from config
  • Loading branch information
mehcode authored Nov 28, 2018
2 parents 802f947 + 2cb768b commit 4d8a1d0
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 4 deletions.
146 changes: 142 additions & 4 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
}
}
13 changes: 13 additions & 0 deletions tests/Settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
40 changes: 40 additions & 0 deletions tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

extern crate config;

#[macro_use]
extern crate serde_derive;

use config::*;

fn make() -> Config {
Expand Down Expand Up @@ -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"
);
}

23 changes: 23 additions & 0 deletions tests/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,});
}

0 comments on commit 4d8a1d0

Please sign in to comment.