1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
use std::{borrow::Cow, ops::Not};
use serde::de::DeserializeOwned;
use crate::{error, error::EnvDeserializationError, value::Parser, Value};
/// Used to configure the behaviour of the environment variable deserialization.
///
/// For information on default behaviours see [`Self::new`].
/// For details on usage see [`Self::build_from_env`] and [`Self::build_from_iter`].
#[derive(Debug, Clone)]
#[must_use]
pub struct Config<'a> {
prefix: Option<Cow<'a, str>>,
case_sensitive: bool,
separator: Cow<'a, str>,
pub(crate) ordered_arrays: bool,
}
impl Default for Config<'static> {
/// See [`Config::new`] for details on the default
fn default() -> Self {
Self::new()
}
}
impl<'a> Config<'a> {
/// Create a new instance of [`Config`] with the following configuration:
/// - No prefix
/// - Case insensitive
/// - A separator of "__" (double underscore)
/// - Sorted arrays
pub const fn new() -> Self {
Self {
prefix: None,
case_sensitive: false,
separator: Cow::Borrowed("__"),
ordered_arrays: true,
}
}
/// Configures the separator used when parsing the environment variable names.
///
/// Defaults to `__` (double underscore)
///
/// ## Example
///
/// Per default (i.e. `__` seperator) an env variable named `foo_bar` would be interpreted as
/// the field with the same name `foo_bar`.
///
/// If you change the seperator to `_`, then in that case it would be interpreted as the
/// following structure:
///
/// ```text
/// foo: {
/// bar: <value>
/// }
/// ```
pub fn with_separator<S>(&mut self, separator: S) -> &mut Self
where
S: Into<Cow<'a, str>>,
{
self.separator = separator.into();
self
}
/// Configures the prefix to strip from environment variables names.
///
/// Environments variables without the prefix are discarded.
///
/// Defaults to no prefix being set. You can switch back to the default via [`Self::without_prefix`].
pub fn with_prefix<S>(&mut self, prefix: S) -> &mut Self
where
S: Into<Cow<'a, str>>,
{
self.prefix = Some(prefix.into());
self
}
/// Resets the [`Config`] to not look for a specific prefix in environment variables names.
///
/// Used to remove the effect of [`Self::with_prefix`].
pub fn without_prefix(&mut self) -> &mut Self {
self.prefix = None;
self
}
/// Configures whether the parsing of environment variables names is case sensitive or not.
///
/// Defaults to case insensitive.
///
/// NB: Only `struct` fields and `enum` variants, as well as any prefix provided via [`Self::with_prefix`] are affected by case sensitivity.
pub fn case_sensitive(&mut self, case_sensitive: bool) -> &mut Self {
self.case_sensitive = case_sensitive;
self
}
/// Configures whether the to treat arrays as ordered by their key.
///
/// Defaults to `true`. If `false`, then array elements will appear in whatever order they are read.
///
/// The ordering strategy places elements as follows:
/// 1. Sort lexicographically, any indices that don't start with a number (e.g. a,b,f)
/// 2. Sort numerically any indices that start with a number (e.g. 1,2,11), these all come after elements from 1.
/// 3. Sort lexicographically any indices that start with the same number (e.g. 1a,1b,1f).
///
/// E.g., the following are already sorted.
/// ```bash
/// export config_array__a="data"
/// export config_array__1="data"
/// export config_array__1b="data"
/// export config_array__2a="data"
/// ```
pub fn ordered_arrays(&mut self, ordered_arrays: bool) -> &mut Self {
self.ordered_arrays = ordered_arrays;
self
}
/// Parse a given `T: Deserialize` from environment variables.
///
/// ## Example
///
/// ```rust
///
///# use serde::{Deserialize, Serialize};
///#
/// #[derive(Serialize, Deserialize, Debug)]
/// enum StaircaseOrientation {
/// Left,
/// Right,
/// }
///
/// #[derive(Serialize, Deserialize, Debug)]
/// struct Config {
/// target_temp: f32,
/// automate_doors: bool,
///
/// staircase_orientation: StaircaseOrientation,
/// }
///
///# #[test]
///# fn parse_from_env() {
///# let vars = [
///# ("target_temp", "25.0"),
///# ("automate_doors", "true"),
///# ("staircase_orientation", "Left"),
///# ];
///#
///# for (key, val) in vars {
///# std::env::set_var(key, val);
///# }
///#
/// let config: Config = envious::Config::default().from_env().unwrap();
///# }
/// ```
pub fn build_from_env<T: DeserializeOwned>(&self) -> Result<T, error::EnvDeserializationError> {
let env_values = std::env::vars();
self.build_from_iter(env_values)
}
/// Parse a given `T: Deserialize` from anything that can be turned into an iterator of key value tuples.
///
/// This function is useful for static deployments or for testing
///
/// ## Example
///
/// ```rust
///
///# use serde::{Deserialize, Serialize};
///#
/// #[derive(Serialize, Deserialize, Debug)]
/// enum StaircaseOrientation {
/// Left,
/// Right,
/// }
///
/// #[derive(Serialize, Deserialize, Debug)]
/// struct Config {
/// target_temp: f32,
/// automate_doors: bool,
///
/// staircase_orientation: StaircaseOrientation,
/// }
///
/// let vars = [
/// ("target_temp", "25.0"),
/// ("automate_doors", "true"),
/// ("staircase_orientation", "Left"),
/// ];
///
/// let config: Config = envious::Config::default().build_from_iter(vars).unwrap();
/// ```
pub fn build_from_iter<T, K, V, I>(&self, iter: I) -> Result<T, error::EnvDeserializationError>
where
T: DeserializeOwned,
K: Into<String>,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
let values = iter.into_iter().map(|(k, v)| (k.into(), v.into()));
let values = values.filter_map(|(mut key, value)| {
// When running case-insensitive we need to make sure that same key with varying casing
// would be stored in the same place. The simplest way to do this is to enforce a specific
// case.
if self.case_sensitive.not() {
key.make_ascii_lowercase();
}
let value = Value::Simple(value);
if let Some(prefix) = &self.prefix {
// If case insensitive, then the prefix will need to match the new key case
let coerced_prefix;
let prefix = if self.case_sensitive {
prefix.as_ref()
} else {
coerced_prefix = prefix.to_ascii_lowercase();
&coerced_prefix
};
let stripped_key = key.strip_prefix(prefix)?.to_owned();
Some((stripped_key, value))
} else {
Some((key, value))
}
});
let parser = self.create_parser(values)?;
T::deserialize(parser)
}
/// Creates a [`Parser`] from its various parts.
fn create_parser<I>(&self, iter: I) -> Result<Parser<'_>, EnvDeserializationError>
where
I: IntoIterator<Item = (String, Value)>,
{
let mut base = Value::Map(vec![]);
for (key, value) in iter {
let path = key.split(self.separator.as_ref()).collect::<Vec<_>>();
if path.len() == 1 {
if let Value::Map(base) = &mut base {
base.push((key, value));
} else {
unreachable!()
}
} else {
base.insert_at(&path, value)?;
}
}
Ok(Parser {
config: self,
current: base,
})
}
/// Given an iterator of keys and values, and a list of keys with corrected casing, converts
/// the keys to the desired cases, thereby making the process case insensitive.
///
/// NB: This uses [`str::eq_ignore_ascii_case`], and therefore has the same limitations.
/// Namely it will not be able to handle differently cased non-ascii characters, such as ß and ẞ.
pub(crate) fn maybe_coerce_case<I, V>(
&self,
values: I,
corrected_cases: &'static [&'static str],
) -> impl Iterator<Item = (String, V)>
where
I: IntoIterator<Item = (String, V)>,
{
let case_sensitive = self.case_sensitive;
values.into_iter().map(move |(key, value)| {
if case_sensitive.not() {
if let Some(&coerced_key) = corrected_cases
.iter()
.find(|item| item.eq_ignore_ascii_case(&key))
{
(coerced_key.to_string(), value)
} else {
(key, value)
}
} else {
(key, value)
}
})
}
}
#[cfg(test)]
mod tests {
use super::{Config, Value};
#[test]
fn convert_list_of_key_vals_to_tree() {
let input = vec![
(String::from("FOO"), Value::simple("BAR")),
(String::from("BAZ"), Value::simple("124")),
(String::from("NESTED__FOO"), Value::simple("true")),
(String::from("NESTED__BAZ"), Value::simple("Hello")),
];
let expected = Value::Map(vec![
(String::from("FOO"), Value::simple("BAR")),
(String::from("BAZ"), Value::simple("124")),
(
String::from("NESTED"),
Value::Map(vec![
(String::from("FOO"), Value::simple("true")),
(String::from("BAZ"), Value::simple("Hello")),
]),
),
]);
let config = Config::new();
let actual = config.create_parser(input).unwrap();
assert_eq!(actual.current, expected);
}
#[test]
fn custom_sep() {
let input = vec![
(String::from("FOO"), Value::simple("bar")),
(String::from("BAZ"), Value::simple("124")),
(String::from("NESTED#FOO"), Value::simple("true")),
(String::from("NESTED#BAZ"), Value::simple("Hello")),
];
let expected = Value::Map(vec![
(String::from("FOO"), Value::simple("bar")),
(String::from("BAZ"), Value::simple("124")),
(
String::from("NESTED"),
Value::Map(vec![
(String::from("FOO"), Value::simple("true")),
(String::from("BAZ"), Value::simple("Hello")),
]),
),
]);
let mut config = Config::new();
let actual = config.with_separator("#").create_parser(input).unwrap();
assert_eq!(actual.current, expected);
}
}