serde_vars/lib.rs
1//! Conveniently expose (environment) variables to your [`serde`] based data structures,
2//! like configurations.
3//!
4//! The main goal of this library is to allow for a very simple but yet powerful mechanism
5//! of dynamically loading single configuration values from (environment) variables.
6//! It is ideal, when you need to include sensitive values in configurations, but don't want
7//! to or can't (e.g. due to repeated or dynamic values) setup manual environment mappings at compile time.
8//!
9//! By implementing a [`serde::de::Deserializer`], this crate works independently of the
10//! serialization format used and with only a few lines of modifications for any configuration.
11//!
12//! # Example
13//!
14//! User provided configuration file:
15//!
16//! ```json
17//! {
18//! "redis": {
19//! "host": "${REDIS_HOST}",
20//! "port": "${REDIS_PORT}"
21//! }
22//! }
23//! ```
24//!
25//! The configuration file contains the variables, no need to decide on variable mappings at compile time.
26//!
27//!
28//! ```
29//! #[derive(Debug, serde::Deserialize)]
30//! struct Config {
31//! redis: Redis,
32//! }
33//!
34//! #[derive(Debug, serde::Deserialize)]
35//! struct Redis {
36//! host: std::net::Ipv4Addr,
37//! port: u16,
38//! }
39//!
40//! fn main() -> Result<(), Box<dyn std::error::Error>> {
41//! let config_path = std::env::args()
42//! .nth(1)
43//! .unwrap_or_else(|| "config.json".to_owned());
44//!
45//! # let config_path = file!();
46//! let config = std::fs::read_to_string(&config_path)?;
47//! # let config = r#"{"redis": {"host": "${REDIS_HOST}", "port": "${REDIS_PORT}"}}"#;
48//! # unsafe { std::env::set_var("REDIS_HOST", "127.0.0.1"); }
49//! # unsafe { std::env::set_var("REDIS_PORT", "6379"); }
50//!
51//! let mut source = serde_vars::EnvSource::default();
52//!
53//! // Before: `let config: Config = serde_json::from_str(&config)?`.
54//! // Now:
55//! let mut de = serde_json::Deserializer::from_str(&config);
56//! let config: Config = serde_vars::deserialize(&mut de, &mut source)?;
57//!
58//! println!("{config:#?}");
59//! # assert_eq!(config.redis.host, std::net::Ipv4Addr::new(127, 0, 0, 1));
60//! # assert_eq!(config.redis.port, 6379);
61//!
62//! Ok(())
63//! }
64//! ```
65//!
66//! # String based Lookups
67//!
68//! `serde-vars` comes packaged with builtin variable sources for environment variables
69//! ([`EnvSource`]), hash maps ([`MapSource`]) as well as a generic string based source
70//! ([`StringSource`]).
71//!
72//! In order to guarantee a consistent parsing behaviour, the [`StringSource`] and all of its
73//! dependent implementations (like [`EnvSource`]) enforce the following format for all values:
74//!
75//! - `true` and `false` are always interpreted as a `bool`.
76//! - Any positive integer is parsed as a `u64`.
77//! - Any negative integer is parsed as a `i64`.
78//! - Any floating point value is parsed as a `f64`.
79//! - Everything else is parsed as a string. In order to be able to specify numbers as strings,
80//! the source recognizes arbitrary values wrapped in `"` as a string. For example `"123"` is
81//! parsed as the literal string `123`.
82//!
83//! For more details read the [`StringSource`] documentation.
84//!
85//! # File based Lookups
86//!
87//! Since `0.3` a [`FileSource`] has been added, it functions very similar to a string based
88//! source, but has slightly different semantics when it comes to parsing and allows loading
89//! arbitrary binary data.
90//!
91//! # Alternatives
92//!
93//! Variable expansion is limited to primitive types and not supported for nested data structures,
94//! this is currently by design. The intention of this library is not to provide another generic
95//! abstraction layer for configurations. If you are looking for a much more powerful mechanism
96//! to load and layer [`serde`] based configurations, you should take a look at
97//! [`figment`](https://docs.rs/figment/) instead.
98
99mod content;
100mod de;
101pub mod source;
102mod value;
103
104pub use self::de::Deserializer;
105pub use self::source::{EnvSource, FileSource, MapSource, StringSource};
106
107/// Entry point. See [crate documentation](crate) for an example.
108pub fn deserialize<'de, D, S, T>(deserializer: D, source: &mut S) -> Result<T, D::Error>
109where
110 D: serde::de::Deserializer<'de>,
111 T: serde::de::Deserialize<'de>,
112 S: source::Source,
113{
114 T::deserialize(self::de::Deserializer::new(deserializer, source))
115}