Skip to main content

deser_env/
lib.rs

1//! Environment variables for deser.
2//!
3//! ```rust
4//! use deser::Deserialize;
5//!
6//! #[derive(Debug, Deserialize)]
7//! struct Config {
8//!     name: String,
9//!     debug: bool,
10//!     server: Server,
11//! }
12//!
13//! #[derive(Debug, Deserialize)]
14//! struct Server {
15//!     port: u16,
16//!     max_connections: Option<u32>,
17//! }
18//!
19//! // `deser_env::from_env::<Config>("APP_")` reads the environment of the
20//! // process, `from_vars` reads the given variables
21//! let config: Config = deser_env::from_vars("APP_", [
22//!     ("APP_NAME", "shop"),
23//!     ("APP_DEBUG", "yes"),
24//!     ("APP_SERVER__PORT", "8080"),
25//!     ("PATH", "/usr/bin"),
26//! ])
27//! .unwrap();
28//! assert_eq!(config.name, "shop");
29//! assert!(config.debug);
30//! assert_eq!(config.server.port, 8080);
31//! assert_eq!(config.server.max_connections, None);
32//! ```
33//!
34//! # Data Model
35//!
36//! The variables with names that start with a prefix (like `APP_`) are a
37//! map.  The prefix is removed and the rest of the name is split at the
38//! separator (`__` by default, see [`DeserializerConfig::set_separator`]) into
39//! nested keys which are lowercased (see [`Case`]):
40//!
41//! | variables                               | deser                                     |
42//! |-----------------------------------------|-------------------------------------------|
43//! | `APP_PORT=80`                           | `{"port": "80"}`                          |
44//! | `APP_MAX_CONNECTIONS=5`                 | `{"max_connections": "5"}`                |
45//! | `APP_SERVER__PORT=80`                   | `{"server": {"port": "80"}}`              |
46//! | `APP_HOSTS__0=a` `APP_HOSTS__1=b`       | `{"hosts": ["a", "b"]}`                   |
47//! | `APP_HOSTS__1=a` `APP_HOSTS__3=b`       | `{"hosts": {"1": "a", "3": "b"}}`         |
48//! | `APP_FEATURES__NEW_UI=on`               | `{"features": {"new_ui": "on"}}`          |
49//!
50//! A single `_` separates words in names, which is why nested keys are
51//! separated with two.  Indexes that start at `0` and have no gaps are
52//! sequences, other indexes are map keys.  Names that start or end with the
53//! separator or contain it twice in a row are taken as they are.  A name
54//! that has a value and nested names (`APP_DB=x` and `APP_DB__POOL=4`) is an
55//! error.  The order of the environment is arbitrary, the variables are
56//! sorted by name.
57//!
58//! Everything in the environment is text, the type of a value is only known
59//! to the type it's deserialized into.  Keys and values are therefore passed
60//! on as [lexical atoms](deser_core::Atom::Lexical) which are parsed by the
61//! types they are delivered to: numbers parse them, strings take them as
62//! they are.  Booleans accept `true`, `yes`, `on` and `1` and `false`, `no`,
63//! `off` and `0`.  Lexical atoms are retained when values are buffered, so
64//! flattened structs and internally tagged and untagged enums work as well.
65//!
66//! ## Empty Values
67//!
68//! A variable that is set to the empty string is the empty value, like
69//! `?page=` in a query string: it's `None` for optionals of types that do
70//! not accept it (`APP_PORT=` is `None` for an `Option<u16>`) and
71//! `Some("")` for an `Option<String>`.  Variables that switch something on
72//! by being set (like `APP_VERBOSE=`) use the
73//! [`Flag`](deser_core::adapters::Flag) adapter which treats the empty
74//! value as `true`:
75//!
76//! ```rust
77//! use deser::adapters::Flag;
78//!
79//! #[derive(deser::Deserialize)]
80//! struct Options {
81//!     #[deser(as = Flag)]
82//!     verbose: bool,
83//!     port: Option<u16>,
84//! }
85//!
86//! let options: Options =
87//!     deser_env::from_vars("APP_", [("APP_VERBOSE", ""), ("APP_PORT", "")])
88//!         .unwrap();
89//! assert!(options.verbose);
90//! assert_eq!(options.port, None);
91//! ```
92//!
93//! ## Lists
94//!
95//! Sequences can be given with indexes (`APP_HOSTS__0`) without changes to
96//! the types.  A single variable (`APP_HOSTS=a`) is a list of one value and
97//! a list without variables is empty (the maps of the environment are
98//! [multimaps](deser_core::ContainerShape::set_multimap), like query
99//! strings).  Lists in a single variable (`APP_HOSTS=a,b,c`) use the
100//! [`Separated`](deser_core::adapters::Separated) adapter, with
101//! [`TrimWhitespace`](deser_core::adapters::TrimWhitespace) to allow spaces
102//! (`a, b, c`).  Both work with all formats, a configuration file can still
103//! give an array:
104//!
105//! ```rust
106//! use deser::adapters::{Separated, TrimWhitespace};
107//!
108//! #[derive(deser::Deserialize)]
109//! struct Config {
110//!     #[deser(as = Separated<',', TrimWhitespace>)]
111//!     hosts: Vec<String>,
112//!     ports: Vec<u16>,
113//!     tags: Vec<String>,
114//! }
115//!
116//! let config: Config = deser_env::from_vars("APP_", [
117//!     ("APP_HOSTS", "a, b"),
118//!     ("APP_PORTS__0", "80"),
119//!     ("APP_PORTS__1", "443"),
120//! ])
121//! .unwrap();
122//! assert_eq!(config.hosts, ["a", "b"]);
123//! assert_eq!(config.ports, [80, 443]);
124//! assert!(config.tags.is_empty());
125//! ```
126//!
127//! # Errors
128//!
129//! Errors carry the name of the variable they refer to (see [`EnvVar`]).
130//! This is also the case for unknown fields that are collected as warnings
131//! (see [`UnknownFields`](deser_core::de::UnknownFields)) and for values
132//! which are buffered:
133//!
134//! ```rust
135//! use deser_env::EnvVar;
136//!
137//! #[derive(Debug, deser::Deserialize)]
138//! struct Config {
139//!     port: u16,
140//! }
141//!
142//! let vars = [("APP_PORT", "http")];
143//! let err = deser_env::from_vars::<Config, _, _, _>("APP_", vars)
144//!     .unwrap_err();
145//! assert_eq!(err.attachment::<EnvVar>().unwrap().name(), "APP_PORT");
146//! assert_eq!(
147//!     err.to_string(),
148//!     "InvalidValue: invalid value \"http\", expected u16 \
149//!      (environment variable APP_PORT)"
150//! );
151//! ```
152//!
153//! # Layering
154//!
155//! Environment variables typically override a configuration file.  With
156//! [`update`](deser_core::de::Deserializer::update) the variables that are
157//! set are applied to an existing value, nested structs are merged:
158//!
159//! ```rust
160//! use deser::de::Deserializer as _;
161//!
162//! #[derive(Debug, deser::Deserialize)]
163//! struct Config {
164//!     server: Server,
165//! }
166//!
167//! #[derive(Debug, deser::Deserialize)]
168//! struct Server {
169//!     host: String,
170//!     port: u16,
171//! }
172//!
173//! let mut config = Config {
174//!     server: Server { host: "localhost".into(), port: 80 },
175//! };
176//! deser_env::Deserializer::from_vars("APP_", [("APP_SERVER__PORT", "8080")])
177//!     .update(&mut config)
178//!     .unwrap();
179//! assert_eq!(config.server.host, "localhost");
180//! assert_eq!(config.server.port, 8080);
181//! ```
182//!
183//! # Platforms
184//!
185//! On Unix values which are not valid unicode are passed on as
186//! [bytes](deser_core::adapters#bytes) (which `Vec<u8>` and `PathBuf`
187//! accept), on other platforms they are an error.  Names that are not valid
188//! unicode are skipped unless they start with the prefix.  On Windows names
189//! are not case sensitive, the prefix is matched ignoring ASCII case.
190//!
191//! Setting environment variables of the running process is `unsafe` (see
192//! [`std::env::set_var`]), [`from_vars`] is a better fit for tests.
193//!
194//! # Serialization
195//!
196//! Values are serialized into name-value pairs (see [`to_vars`] and
197//! [`SerializerConfig`]), for instance to pass a configuration to a child
198//! process with [`Command::envs`](std::process::Command::envs).
199//!
200//! # Features
201//!
202//! * `speedups` (enabled by default): has no effect yet, it exists so
203//!   that all formats have it.
204#![doc(html_logo_url = "https://raw.githubusercontent.com/mitsuhiko/deser/main/artwork/logo.svg")]
205
206mod de;
207mod ser;
208
209use std::borrow::Cow;
210use std::fmt;
211use std::sync::Arc;
212
213use deser_core::Text;
214use deser_core::de::{
215    Deserialize, DeserializeDriver, DeserializeOwned, LexicalRules, missing_multimap_value,
216};
217use deser_core::{Atom, Error, ErrorAttachment, ErrorKind};
218
219pub use self::de::{Deserializer, DeserializerConfig, DeserializerConfigBuilder};
220pub use self::ser::{SerializerConfig, SerializerConfigBuilder, to_vars};
221
222/// How the case of names maps onto keys.
223///
224/// ```
225/// use std::collections::BTreeMap;
226/// use deser_env::{Case, DeserializerConfig, SerializerConfig};
227///
228/// let vars = [("APP_Server__Port", "80")];
229/// let value: BTreeMap<String, BTreeMap<String, u16>> =
230///     deser_env::from_vars("APP_", vars).unwrap();
231/// assert_eq!(value["server"]["port"], 80);
232///
233/// let preserve = DeserializerConfig::builder().case(Case::Preserve).build();
234/// let value: BTreeMap<String, BTreeMap<String, u16>> =
235///     preserve.from_vars("APP_", vars).unwrap();
236/// assert_eq!(value["Server"]["Port"], 80);
237///
238/// let vars = deser_env::to_vars("APP_", &value).unwrap();
239/// assert_eq!(vars, [("APP_SERVER__PORT".to_string(), "80".to_string())]);
240/// ```
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
242#[non_exhaustive]
243pub enum Case {
244    /// Names are upper case and keys lower case.
245    ///
246    /// When deserializing names are lowercased (ASCII only), so
247    /// `APP_SERVER__PORT` (and `APP_server__port`) is `server.port`.  When
248    /// serializing keys are uppercased.  Keys of maps that are not lower
249    /// case do not read back the same.
250    #[default]
251    Upper,
252    /// Names and keys are the same.
253    ///
254    /// This is useful if the types have names in upper case, for instance
255    /// with `#[deser(alias_all = "SCREAMING_SNAKE_CASE")]`.
256    Preserve,
257}
258
259/// The environment variable an error refers to.
260///
261/// Errors of values and keys that come from environment variables carry
262/// this [attachment](deser_core::ErrorAttachment).  For keys that more than
263/// one variable share (like `server` in `APP_SERVER__HOST` and
264/// `APP_SERVER__PORT`) it's the name up to the key (`APP_SERVER`).  Errors
265/// that do not come from a single variable (like missing fields) have none.
266///
267/// ```
268/// use deser_env::EnvVar;
269///
270/// #[derive(Debug, deser::Deserialize)]
271/// struct Config {
272///     server: Server,
273/// }
274///
275/// #[derive(Debug, deser::Deserialize)]
276/// #[deser(deny_unknown_fields)]
277/// struct Server {
278///     port: u16,
279/// }
280///
281/// let err = deser_env::from_vars::<Config, _, _, _>(
282///     "APP_",
283///     [("APP_SERVER__PROT", "80")],
284/// )
285/// .unwrap_err();
286/// assert_eq!(err.message(), "unknown field `prot`, expected `port`");
287/// assert_eq!(
288///     err.attachment::<EnvVar>().unwrap().name(),
289///     "APP_SERVER__PROT"
290/// );
291/// ```
292#[derive(Debug, Clone, PartialEq, Eq)]
293pub struct EnvVar {
294    name: Arc<str>,
295}
296
297impl EnvVar {
298    pub(crate) fn new(name: Arc<str>) -> EnvVar {
299        EnvVar { name }
300    }
301
302    /// Returns the name of the variable.
303    pub fn name(&self) -> &str {
304        &self.name
305    }
306}
307
308impl ErrorAttachment for EnvVar {
309    fn fmt_context(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        write!(f, " (environment variable {})", self.name)
311    }
312}
313
314/// Deserializes a value from the environment variables with a prefix.
315///
316/// This reads the environment of the process with the default
317/// [`DeserializerConfig`], see the [crate documentation](crate) for how
318/// variables map onto deser.  Without a prefix (`""`) all variables are
319/// read.  Then `PATH`, `HOME` and the like are keys too which fails for
320/// types that deny unknown fields.
321///
322/// ```no_run
323/// #[derive(deser::Deserialize)]
324/// struct Config {
325///     port: u16,
326/// }
327///
328/// // reads `APP_PORT`
329/// let config: Config = deser_env::from_env("APP_").unwrap();
330/// ```
331pub fn from_env<T: DeserializeOwned>(prefix: &str) -> Result<T, Error> {
332    Deserializer::from_env(prefix).deserialize()
333}
334
335/// Deserializes a value from the given variables with a prefix.
336///
337/// The variables are name-value pairs, for instance from
338/// [`std::env::vars`] or a file, which are deserialized as if they were the
339/// environment (see [`from_env`]).  Values that are given borrowed are
340/// passed on borrowed.
341///
342/// ```
343/// use std::collections::BTreeMap;
344///
345/// let value: BTreeMap<String, Vec<u32>> =
346///     deser_env::from_vars("", [("A__0", "1"), ("A__1", "2"), ("B", "3")])
347///         .unwrap();
348/// assert_eq!(value["a"], [1, 2]);
349/// assert_eq!(value["b"], [3]);
350/// ```
351pub fn from_vars<'a, T, I, K, V>(prefix: &str, vars: I) -> Result<T, Error>
352where
353    T: Deserialize<'a>,
354    I: IntoIterator<Item = (K, V)>,
355    K: Into<Cow<'a, str>>,
356    V: Into<Cow<'a, str>>,
357{
358    Deserializer::from_vars(prefix, vars).deserialize()
359}
360
361/// Deserializes the value of a single environment variable.
362///
363/// The value is parsed like the values of [`from_env`] (numbers and
364/// booleans parse, the empty value is `None` for optionals of types that do
365/// not accept it).  Collections (like `Vec<T>`) are a collection of the
366/// value, like the fields of structs.  If the variable is not set, types
367/// that can be missing (like `Option<T>`) use their missing value and
368/// collections are empty, other types fail.  Errors carry the name of the
369/// variable (see [`EnvVar`]).
370///
371/// ```no_run
372/// let port: u16 = deser_env::var("PORT").unwrap();
373/// let workers: Option<usize> = deser_env::var("WORKERS").unwrap();
374/// ```
375pub fn var<T: DeserializeOwned>(name: &str) -> Result<T, Error> {
376    let attach = |mut err: Error| {
377        err.set_attachment(EnvVar::new(name.into()));
378        err
379    };
380    let value = match std::env::var_os(name) {
381        Some(value) => value,
382        None => {
383            // collections are empty (like the collections of missing
384            // keys in `from_env`)
385            return missing_multimap_value::<T>().ok_or_else(|| {
386                attach(Error::new(
387                    ErrorKind::MissingField,
388                    "environment variable is not set",
389                ))
390            });
391        }
392    };
393    let mut out = None;
394    {
395        // the variable stands for a key given once: collections (like
396        // `Vec<T>`) are one value
397        let mut driver = DeserializeDriver::multimap_value(&mut out);
398        LexicalRules::LENIENT.set_default(driver.state_mut());
399        match value.into_string() {
400            Ok(text) => driver.emit(Atom::Lexical(Text::borrowed(&text))),
401            Err(value) => match de::os_bytes(value) {
402                Some(bytes) => driver.emit(Atom::Bytes(deser_core::Bytes::borrowed(&bytes))),
403                None => Err(Error::new(ErrorKind::Syntax, "value is not valid unicode")),
404            },
405        }
406        .map_err(attach)?;
407    }
408    out.ok_or_else(|| attach(Error::new(ErrorKind::InvalidState, "no value")))
409}