justconfig/sources/
env.rs

1//! Environment source
2//!
3//! The environment source supplies environment variables to the configuration
4//! system. By being its own source it can be ordered before or after other
5//! sources. This way environment variables can override configuration settings
6//! or can be used as a fallback.
7//!
8//! The environment source uses a mapping to translate the names of environment
9//! variables into configuration paths. This mapping is passed to the
10//! [`new`](Env::new) method. Environment variables not present within this
11//! mapping are inaccessible by the configuration system. Adding a mapping for
12//! an environment variable does *not* make sure it really exists.
13//! 
14//! ## Example
15//! 
16//! ```rust
17//! use justconfig::Config;
18//! use justconfig::ConfPath;
19//! use std::ffi::OsStr;
20//! use justconfig::item::ValueExtractor;
21//! use justconfig::sources::env::Env;
22//!
23//! let mut conf = Config::default();
24//!
25//! conf.add_source(Env::new(&[
26//!   (ConfPath::from(&["Path"]), OsStr::new("PATH")),
27//!   (ConfPath::from(&["HomeDir"]), OsStr::new("HOME"))
28//! ]));
29//!
30//! // Read the path from the environment
31//! let path: String = conf.get(ConfPath::from(&["Path"])).value().unwrap();
32//! ```
33use crate::source::Source;
34use crate::item::{SourceLocation, StringItem, Value};
35use crate::confpath::ConfPath;
36use std::ffi::{OsStr, OsString};
37use std::collections::hash_map::HashMap;
38use std::fmt;
39use std::env;
40use std::rc::Rc;
41
42/// Source location for the Env configuration source.
43/// 
44/// This value is used to store the source of every configuration value for
45/// use in error messages.
46#[derive(Debug)]
47struct EnvSourceLocation {
48	env_name: OsString
49}
50
51impl EnvSourceLocation {
52	pub fn new(env_name: &OsStr) -> Rc<Self> {
53		Rc::new(Self {
54			env_name: env_name.to_owned()
55		})
56	}
57}
58
59impl fmt::Display for EnvSourceLocation {
60	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61		write!(f, "env:{}", self.env_name.to_string_lossy())
62	}
63}
64
65impl SourceLocation for EnvSourceLocation {}
66
67/// Implements the environment source.
68pub struct Env {
69	env_mapping: HashMap<ConfPath, OsString>
70}
71
72impl Env {
73	/// Creates a new environment source.
74	///
75	/// For creating the environment source a mapping between the configuration
76	/// key and the name of the environment variable has to be created. This is done
77	/// by passing a slice of tuples. The first element of the tuple defines the
78	/// configuration path of the environment value and the second element defines
79	/// the name of the environment variable.
80	///
81	/// See the [`env`](mod@env) module for more information.
82	pub fn new(env_mapping: &[(ConfPath, &OsStr)]) -> Box<Self> {
83		Box::new(Self {
84			env_mapping: env_mapping.iter().map(|m| (m.0.clone(), m.1.to_owned())).collect()
85		})
86	}
87}
88
89impl Source for Env {
90	fn get(&self, key: ConfPath) -> Option<StringItem> {
91		if let Some(env_name) = self.env_mapping.get(&key) {
92			env::var(env_name).ok().map(|v| StringItem::from(key, &[Value::new(v, EnvSourceLocation::new(env_name))]))
93		} else {
94			None
95		}
96	}
97}
98
99#[cfg(test)]
100mod tests {
101	use super::*;
102	use crate::Config;
103	use crate::error::ConfigError;
104	use crate::item::ValueExtractor;
105	use std::env;
106
107	fn prepare_test_config() -> Config{
108		env::set_var(OsStr::new("existing_value"), OsStr::new("existing_value"));
109
110		let mut c = Config::default();
111
112		c.add_source(Env::new(&[
113			(ConfPath::from(&["testA"]), OsStr::new("existing_value")),
114			(ConfPath::from(&["testB"]), OsStr::new("non_existant_value"))
115		]));
116
117		c
118	}
119
120	#[test]
121	fn existing_value() {
122		let c = prepare_test_config();
123		assert_eq!((c.get(ConfPath::from(&["testA"])).value() as Result<String, ConfigError>).unwrap(), "existing_value");
124	}
125
126	#[test]
127	#[should_panic(expected = "ValueNotFound")]
128	fn non_existant_env_name() {
129		let c = prepare_test_config();
130
131		(c.get(ConfPath::from(&["testB"])).value() as Result<String, ConfigError>).unwrap();
132	}
133
134	#[test]
135	#[should_panic(expected = "ValueNotFound")]
136	fn non_existant_value() {
137		let c = prepare_test_config();
138
139		(c.get(ConfPath::from(&["testC"])).value() as Result<String, ConfigError>).unwrap();
140	}
141}