diff --git a/Cargo.toml b/Cargo.toml index 4b37ce2..49c082c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ coveralls = { repository = "softprops/envy" } travis-ci = { repository = "softprops/envy" } [dependencies] +regex = "1.4.1" serde = "1.0" [dev-dependencies] -serde = { version = "1.0", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } diff --git a/examples/prefixed.rs b/examples/prefixed.rs index f0ac196..0196278 100644 --- a/examples/prefixed.rs +++ b/examples/prefixed.rs @@ -1,13 +1,19 @@ +use std::collections::HashMap; + use serde::Deserialize; #[derive(Deserialize)] struct Config { bar: Option, + foo: HashMap, // Must be formatted like "{key:value,key2:value2}" } fn main() { match envy::prefixed("FOO_").from_env::() { - Ok(config) => println!("provided config.bar {:?}", config.bar), + Ok(config) => println!( + "provided config.bar {:?} and config.foo {:?}", + config.bar, config.foo + ), Err(err) => println!("error parsing config from env: {}", err), } } diff --git a/src/lib.rs b/src/lib.rs index ca0371c..2f56eae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ //! } //! ``` +use regex::Regex; use serde::de::{ self, value::{MapDeserializer, SeqDeserializer}, @@ -75,6 +76,7 @@ struct Vars(Iter) where Iter: IntoIterator; +#[derive(Debug)] struct Val(String, String); impl<'de> IntoDeserializer<'de, Error> for Val { @@ -85,6 +87,7 @@ impl<'de> IntoDeserializer<'de, Error> for Val { } } +#[derive(Debug)] struct VarName(String); impl<'de> IntoDeserializer<'de, Error> for VarName { @@ -191,9 +194,35 @@ impl<'de> de::Deserializer<'de> for Val { visitor.visit_enum(self.1.into_deserializer()) } + fn deserialize_map( + self, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + let hashmap_reg = Regex::new(r#"(?P[^{=]+)=(?P[^,}]*),?"#).unwrap(); + if !self.1.is_empty() && !hashmap_reg.is_match(&self.1) + { + return Err(Error::Custom(format!( + "{} is malformated for a map (must be 'key=value,key2=value2')", + self.0 + ))); + } + + let map = hashmap_reg.captures_iter(&self.1).map(|ref caps| { + ( + caps["key"].trim().to_string(), + caps["value"].trim().to_string(), + ) + }); + + visitor.visit_map(MapDeserializer::new(map)) + } + serde::forward_to_deserialize_any! { char str string unit - bytes byte_buf map unit_struct tuple_struct + bytes byte_buf unit_struct tuple_struct identifier tuple ignored_any struct } @@ -225,12 +254,11 @@ impl<'de> de::Deserializer<'de> for VarName { serde::forward_to_deserialize_any! { char str string unit seq option - bytes byte_buf map unit_struct tuple_struct - identifier tuple ignored_any enum + bytes byte_buf unit_struct tuple_struct + identifier tuple ignored_any enum map struct bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 } } - /// A deserializer for env vars struct Deserializer<'de, Iter: Iterator> { inner: MapDeserializer<'de, Vars, Error>, @@ -270,9 +298,8 @@ impl<'de, Iter: Iterator> de::Deserializer<'de> serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq - bytes byte_buf unit_struct tuple_struct + bytes byte_buf unit_struct tuple_struct struct identifier tuple ignored_any option newtype_struct enum - struct } } @@ -317,13 +344,10 @@ impl<'a> Prefixed<'a> { T: de::DeserializeOwned, Iter: IntoIterator, { - crate::from_iter(iter.into_iter().filter_map(|(k, v)| { - if k.starts_with(self.0.as_ref()) { - Some((k.trim_start_matches(self.0.as_ref()).to_owned(), v)) - } else { - None - } - })) + crate::from_iter( + iter.into_iter() + .filter_map(|(k, v)| k.strip_prefix(self.0.as_ref()).map(|k| (k.to_string(), v))), + ) } } @@ -397,6 +421,7 @@ mod tests { size: Size, provided: Option, newtype: CustomNewType, + map: Option>, } #[test] @@ -421,7 +446,8 @@ mod tests { debug_mode: false, size: Size::Small, provided: Some(String::from("test")), - newtype: CustomNewType(42) + newtype: CustomNewType(42), + map: None, } ), Err(e) => panic!("{:#?}", e), @@ -465,7 +491,15 @@ mod tests { (String::from("APP_SIZE"), String::from("small")), (String::from("APP_PROVIDED"), String::from("test")), (String::from("APP_NEWTYPE"), String::from("42")), + ( + String::from("APP_MAP"), + String::from("key=value, key2=value2"), + ), ]; + let mut map = HashMap::new(); + map.insert(String::from("key"), String::from("value")); + map.insert(String::from("key2"), String::from("value2")); + match prefixed("APP_").from_iter::<_, Foo>(data) { Ok(actual) => assert_eq!( actual, @@ -478,7 +512,8 @@ mod tests { debug_mode: false, size: Size::Small, provided: Some(String::from("test")), - newtype: CustomNewType(42) + newtype: CustomNewType(42), + map: Some(map) } ), Err(e) => panic!("{:#?}", e),