envy/
lib.rs

1//! Envy is a library for deserializing environment variables into typesafe structs
2//!
3//! # Examples
4//!
5//! A typical usecase for envy is deserializing configuration store in an process' environment into a struct
6//! whose fields map to the names of env vars.
7//!
8//! Serde makes it easy to provide a deserializable struct with its [deriveable Deserialize](https://serde.rs/derive.html)
9//! procedural macro.
10//!
11//! Simply ask for an instance of that struct from envy's `from_env` function.
12//!
13//! ```no_run
14//! use serde::Deserialize;
15//!
16//! #[derive(Deserialize, Debug)]
17//! struct Config {
18//!     foo: u16,
19//!     bar: bool,
20//!     baz: String,
21//!     boom: Option<u64>,
22//! }
23//!
24//! match envy::from_env::<Config>() {
25//!     Ok(config) => println!("{:#?}", config),
26//!     Err(error) => eprintln!("{:#?}", error),
27//! }
28//! ```
29//!
30//! Special treatment is given to collections. For config fields that store a `Vec` of values,
31//! use an env var that uses a comma separated value.
32//!
33//! All serde modifiers should work as is.
34//!
35//! Enums with unit variants can be used as values:
36//!
37//! ```no_run
38//! # use serde::Deserialize;
39//!
40//! #[derive(Deserialize, Debug, PartialEq)]
41//! #[serde(rename_all = "lowercase")]
42//! pub enum Size {
43//!     Small,
44//!     Medium,
45//!     Large,
46//! }
47//!
48//! #[derive(Deserialize, Debug)]
49//! struct Config {
50//!     size: Size,
51//! }
52//!
53//! // set env var for size as `SIZE=medium`
54//! match envy::from_env::<Config>() {
55//!     Ok(config) => println!("{:#?}", config),
56//!     Err(error) => eprintln!("{:#?}", error),
57//! }
58//! ```
59
60use serde::de::{
61    self,
62    value::{MapDeserializer, SeqDeserializer},
63    IntoDeserializer,
64};
65use std::{
66    borrow::Cow,
67    env,
68    iter::{empty, IntoIterator},
69};
70
71// Ours
72mod error;
73pub use crate::error::Error;
74
75/// A type result type specific to `envy::Errors`
76pub type Result<T> = std::result::Result<T, Error>;
77
78struct Vars<Iter>(Iter)
79where
80    Iter: IntoIterator<Item = (String, String)>;
81
82struct Val(String, String);
83
84impl<'de> IntoDeserializer<'de, Error> for Val {
85    type Deserializer = Self;
86
87    fn into_deserializer(self) -> Self::Deserializer {
88        self
89    }
90}
91
92struct VarName(String);
93
94impl<'de> IntoDeserializer<'de, Error> for VarName {
95    type Deserializer = Self;
96
97    fn into_deserializer(self) -> Self::Deserializer {
98        self
99    }
100}
101
102impl<Iter: Iterator<Item = (String, String)>> Iterator for Vars<Iter> {
103    type Item = (VarName, Val);
104
105    fn next(&mut self) -> Option<Self::Item> {
106        self.0
107            .next()
108            .map(|(k, v)| (VarName(k.to_lowercase()), Val(k, v)))
109    }
110}
111
112macro_rules! forward_parsed_values {
113    ($($ty:ident => $method:ident,)*) => {
114        $(
115            fn $method<V>(self, visitor: V) -> Result<V::Value>
116                where V: de::Visitor<'de>
117            {
118                match self.1.parse::<$ty>() {
119                    Ok(val) => val.into_deserializer().$method(visitor),
120                    Err(e) => Err(de::Error::custom(format_args!("{} while parsing value '{}' provided by {}", e, self.1, self.0)))
121                }
122            }
123        )*
124    }
125}
126
127impl<'de> de::Deserializer<'de> for Val {
128    type Error = Error;
129    fn deserialize_any<V>(
130        self,
131        visitor: V,
132    ) -> Result<V::Value>
133    where
134        V: de::Visitor<'de>,
135    {
136        self.1.into_deserializer().deserialize_any(visitor)
137    }
138
139    fn deserialize_seq<V>(
140        self,
141        visitor: V,
142    ) -> Result<V::Value>
143    where
144        V: de::Visitor<'de>,
145    {
146        // std::str::split doesn't work as expected for our use case: when we
147        // get an empty string we want to produce an empty Vec, but split would
148        // still yield an iterator with an empty string in it. So we need to
149        // special case empty strings.
150        if self.1.is_empty() {
151            SeqDeserializer::new(empty::<Val>()).deserialize_seq(visitor)
152        } else {
153            let values = self.1.split(',').map(|v| Val(self.0.clone(), v.to_owned()));
154            SeqDeserializer::new(values).deserialize_seq(visitor)
155        }
156    }
157
158    fn deserialize_option<V>(
159        self,
160        visitor: V,
161    ) -> Result<V::Value>
162    where
163        V: de::Visitor<'de>,
164    {
165        visitor.visit_some(self)
166    }
167
168    forward_parsed_values! {
169        bool => deserialize_bool,
170        u8 => deserialize_u8,
171        u16 => deserialize_u16,
172        u32 => deserialize_u32,
173        u64 => deserialize_u64,
174        i8 => deserialize_i8,
175        i16 => deserialize_i16,
176        i32 => deserialize_i32,
177        i64 => deserialize_i64,
178        f32 => deserialize_f32,
179        f64 => deserialize_f64,
180    }
181
182    #[inline]
183    fn deserialize_newtype_struct<V>(
184        self,
185        _: &'static str,
186        visitor: V,
187    ) -> Result<V::Value>
188    where
189        V: serde::de::Visitor<'de>,
190    {
191        visitor.visit_newtype_struct(self)
192    }
193
194    fn deserialize_enum<V>(
195        self,
196        _name: &'static str,
197        _variants: &'static [&'static str],
198        visitor: V,
199    ) -> Result<V::Value>
200    where
201        V: de::Visitor<'de>,
202    {
203        visitor.visit_enum(self.1.into_deserializer())
204    }
205
206    serde::forward_to_deserialize_any! {
207        char str string unit
208        bytes byte_buf map unit_struct tuple_struct
209        identifier tuple ignored_any
210        struct
211    }
212}
213
214impl<'de> de::Deserializer<'de> for VarName {
215    type Error = Error;
216    fn deserialize_any<V>(
217        self,
218        visitor: V,
219    ) -> Result<V::Value>
220    where
221        V: de::Visitor<'de>,
222    {
223        self.0.into_deserializer().deserialize_any(visitor)
224    }
225
226    #[inline]
227    fn deserialize_newtype_struct<V>(
228        self,
229        _: &'static str,
230        visitor: V,
231    ) -> Result<V::Value>
232    where
233        V: serde::de::Visitor<'de>,
234    {
235        visitor.visit_newtype_struct(self)
236    }
237
238    serde::forward_to_deserialize_any! {
239        char str string unit seq option
240        bytes byte_buf map unit_struct tuple_struct
241        identifier tuple ignored_any enum
242        struct bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64
243    }
244}
245
246/// A deserializer for env vars
247struct Deserializer<'de, Iter: Iterator<Item = (String, String)>> {
248    inner: MapDeserializer<'de, Vars<Iter>, Error>,
249}
250
251impl<'de, Iter: Iterator<Item = (String, String)>> Deserializer<'de, Iter> {
252    fn new(vars: Iter) -> Self {
253        Deserializer {
254            inner: MapDeserializer::new(Vars(vars)),
255        }
256    }
257}
258
259impl<'de, Iter: Iterator<Item = (String, String)>> de::Deserializer<'de>
260    for Deserializer<'de, Iter>
261{
262    type Error = Error;
263    fn deserialize_any<V>(
264        self,
265        visitor: V,
266    ) -> Result<V::Value>
267    where
268        V: de::Visitor<'de>,
269    {
270        self.deserialize_map(visitor)
271    }
272
273    fn deserialize_map<V>(
274        self,
275        visitor: V,
276    ) -> Result<V::Value>
277    where
278        V: de::Visitor<'de>,
279    {
280        visitor.visit_map(self.inner)
281    }
282
283    serde::forward_to_deserialize_any! {
284        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq
285        bytes byte_buf unit_struct tuple_struct
286        identifier tuple ignored_any option newtype_struct enum
287        struct
288    }
289}
290
291/// Deserializes a type based on information stored in env variables
292pub fn from_env<T>() -> Result<T>
293where
294    T: de::DeserializeOwned,
295{
296    from_iter(env::vars())
297}
298
299/// Deserializes a type based on an iterable of `(String, String)`
300/// representing keys and values
301pub fn from_iter<Iter, T>(iter: Iter) -> Result<T>
302where
303    T: de::DeserializeOwned,
304    Iter: IntoIterator<Item = (String, String)>,
305{
306    T::deserialize(Deserializer::new(iter.into_iter()))
307}
308
309/// A type which filters env vars with a prefix for use as serde field inputs
310///
311/// These types are created with with the [prefixed](fn.prefixed.html) module function
312pub struct Prefixed<'a>(Cow<'a, str>);
313
314impl<'a> Prefixed<'a> {
315    /// Deserializes a type based on prefixed env variables
316    pub fn from_env<T>(&self) -> Result<T>
317    where
318        T: de::DeserializeOwned,
319    {
320        self.from_iter(env::vars())
321    }
322
323    /// Deserializes a type based on prefixed (String, String) tuples
324    pub fn from_iter<Iter, T>(
325        &self,
326        iter: Iter,
327    ) -> Result<T>
328    where
329        T: de::DeserializeOwned,
330        Iter: IntoIterator<Item = (String, String)>,
331    {
332        crate::from_iter(iter.into_iter().filter_map(|(k, v)| {
333            if k.starts_with(self.0.as_ref()) {
334                Some((k.trim_start_matches(self.0.as_ref()).to_owned(), v))
335            } else {
336                None
337            }
338        }))
339    }
340}
341
342/// Produces a instance of `Prefixed` for prefixing env variable names
343///
344/// # Example
345///
346/// ```no_run
347/// use serde::Deserialize;
348///
349/// #[derive(Deserialize, Debug)]
350/// struct Config {
351///     foo: u16,
352///     bar: bool,
353///     baz: String,
354///     boom: Option<u64>,
355/// }
356///
357/// // all env variables will be expected to be prefixed with APP_
358/// // i.e. APP_FOO, APP_BAR, ect
359/// match envy::prefixed("APP_").from_env::<Config>() {
360///     Ok(config) => println!("{:#?}", config),
361///     Err(error) => eprintln!("{:#?}", error),
362/// }
363/// ```
364pub fn prefixed<'a, C>(prefix: C) -> Prefixed<'a>
365where
366    C: Into<Cow<'a, str>>,
367{
368    Prefixed(prefix.into())
369}
370
371#[cfg(test)]
372mod tests {
373    use super::*;
374    use serde::Deserialize;
375    use std::collections::HashMap;
376
377    #[derive(Deserialize, Debug, PartialEq)]
378    #[serde(rename_all = "lowercase")]
379    pub enum Size {
380        Small,
381        Medium,
382        Large,
383    }
384
385    impl Default for Size {
386        fn default() -> Size {
387            Size::Medium
388        }
389    }
390
391    pub fn default_kaboom() -> u16 {
392        8080
393    }
394
395    #[derive(Deserialize, Debug, PartialEq)]
396    pub struct CustomNewType(u32);
397
398    #[derive(Deserialize, Debug, PartialEq)]
399    pub struct Foo {
400        bar: String,
401        baz: bool,
402        zoom: Option<u16>,
403        doom: Vec<u64>,
404        boom: Vec<String>,
405        #[serde(default = "default_kaboom")]
406        kaboom: u16,
407        #[serde(default)]
408        debug_mode: bool,
409        #[serde(default)]
410        size: Size,
411        provided: Option<String>,
412        newtype: CustomNewType,
413    }
414
415    #[test]
416    fn deserialize_from_iter() {
417        let data = vec![
418            (String::from("BAR"), String::from("test")),
419            (String::from("BAZ"), String::from("true")),
420            (String::from("DOOM"), String::from("1,2,3")),
421            // Empty string should result in empty vector.
422            (String::from("BOOM"), String::from("")),
423            (String::from("SIZE"), String::from("small")),
424            (String::from("PROVIDED"), String::from("test")),
425            (String::from("NEWTYPE"), String::from("42")),
426        ];
427        match from_iter::<_, Foo>(data) {
428            Ok(actual) => assert_eq!(
429                actual,
430                Foo {
431                    bar: String::from("test"),
432                    baz: true,
433                    zoom: None,
434                    doom: vec![1, 2, 3],
435                    boom: vec![],
436                    kaboom: 8080,
437                    debug_mode: false,
438                    size: Size::Small,
439                    provided: Some(String::from("test")),
440                    newtype: CustomNewType(42)
441                }
442            ),
443            Err(e) => panic!("{:#?}", e),
444        }
445    }
446
447    #[test]
448    fn fails_with_missing_value() {
449        let data = vec![
450            (String::from("BAR"), String::from("test")),
451            (String::from("BAZ"), String::from("true")),
452        ];
453        match from_iter::<_, Foo>(data) {
454            Ok(_) => panic!("expected failure"),
455            Err(e) => assert_eq!(e, Error::MissingValue("doom")),
456        }
457    }
458
459    #[test]
460    fn fails_with_invalid_type() {
461        let data = vec![
462            (String::from("BAR"), String::from("test")),
463            (String::from("BAZ"), String::from("notabool")),
464            (String::from("DOOM"), String::from("1,2,3")),
465        ];
466        match from_iter::<_, Foo>(data) {
467            Ok(_) => panic!("expected failure"),
468            Err(e) => assert_eq!(
469                e,
470                Error::Custom(String::from("provided string was not `true` or `false` while parsing value \'notabool\' provided by BAZ"))
471            ),
472        }
473    }
474
475    #[test]
476    fn deserializes_from_prefixed_fieldnames() {
477        let data = vec![
478            (String::from("APP_BAR"), String::from("test")),
479            (String::from("APP_BAZ"), String::from("true")),
480            (String::from("APP_DOOM"), String::from("")),
481            (String::from("APP_BOOM"), String::from("4,5")),
482            (String::from("APP_SIZE"), String::from("small")),
483            (String::from("APP_PROVIDED"), String::from("test")),
484            (String::from("APP_NEWTYPE"), String::from("42")),
485        ];
486        match prefixed("APP_").from_iter::<_, Foo>(data) {
487            Ok(actual) => assert_eq!(
488                actual,
489                Foo {
490                    bar: String::from("test"),
491                    baz: true,
492                    zoom: None,
493                    doom: vec![],
494                    boom: vec!["4".to_string(), "5".to_string()],
495                    kaboom: 8080,
496                    debug_mode: false,
497                    size: Size::Small,
498                    provided: Some(String::from("test")),
499                    newtype: CustomNewType(42)
500                }
501            ),
502            Err(e) => panic!("{:#?}", e),
503        }
504    }
505
506    #[test]
507    fn prefixed_strips_prefixes() {
508        let mut expected = HashMap::new();
509        expected.insert("foo".to_string(), "bar".to_string());
510        assert_eq!(
511            prefixed("PRE_").from_iter(vec![("PRE_FOO".to_string(), "bar".to_string())]),
512            Ok(expected)
513        );
514    }
515
516    #[test]
517    fn prefixed_doesnt_parse_non_prefixed() {
518        let mut expected = HashMap::new();
519        expected.insert("foo".to_string(), 12);
520        assert_eq!(
521            prefixed("PRE_").from_iter(vec![
522                ("FOO".to_string(), "asd".to_string()),
523                ("PRE_FOO".to_string(), "12".to_string())
524            ]),
525            Ok(expected)
526        );
527    }
528}