config_generator/
lib.rs

1//! The [ConfigGenerator] derive macro is intended to simplify the process of writing
2//! configuration loading boilerplate. It currently provides two main features:
3//! TOML loading, and loading of values from environment variables.
4//!
5//! # Features
6//!
7//! By default, the `load_toml` feature is enabled, supplying a `with_toml` function implementation
8//! on the input struct, and consequently requiring the [serde] and [toml] crates. This feature can
9//! be disabled, and these crates removed, if the user so chooses.
10//!
11//! # Usage
12//!
13//! The macro is used by applying it to a struct via `#[derive(ConfigGenerator)`.
14//!
15//! ```
16//! use config_generator::ConfigGenerator;
17//!
18//! #[derive(ConfigGenerator, Default)]
19//! struct Config {
20//!   #[env_key = "NAME_ENV_KEY"]
21//!   pub name: String,
22//!   #[env_key = "THREAD_COUNT"]
23//!   pub thread_count: u32,
24//!   #[env_key = "ALLOWED_ORIGINS"]
25//!   pub allowed_origins: Vec<String>,
26//!   #[env_key = "ASSETS_PATH"]
27//!   pub assets_path: Option<String>,
28//! }
29//! ```
30//!
31//! This generates a private struct called `Optional[InputStruct]`. Thus, in the example above,
32//! `OptionalConfig` would be generated. The generated optional struct is identical to the input
33//! struct except that each of its fields is an [Option<T>]. This field is used internally to track
34//! loaded data and apply it to the configuration struct when the user calls the `with_toml` or
35//! `with_environment` functions to load values.
36//!
37//! Note that the user is expected to derive [Default] or implement some other means of obtaining
38//! a starting point for configuration loading, such as a `new` function.
39//!
40//! For example, the following basic example loads values from an input toml file.
41//! ```
42//! use config_generator::ConfigGenerator;
43//!
44//! #[derive(ConfigGenerator, Default)]
45//! struct Config {
46//!   pub name: String,
47//! }
48//!
49//! fn main() {
50//!   let config = Config::default().with_toml(&"tests/tomls/simple_config.toml");
51//! }
52//! ```
53//!
54//! Similarly, the following example would load values from the environment, specifically
55//! from the `NAME_KEY` environment variable annotated above the `name` field.
56//! ```
57//! use config_generator::ConfigGenerator;
58//!
59//! #[derive(ConfigGenerator, Default)]
60//! struct Config {
61//!   #[env_key = "NAME_KEY"]
62//!   pub name: String,
63//! }
64//!
65//! fn main() {
66//!   let config = Config::default().with_env();
67//! }
68//! ```
69//!
70//! These functions can also be composed together. When composed, a latter-executed function
71//! will superimpose loaded values on the prior config.
72//! ```
73//! use config_generator::ConfigGenerator;
74//!
75//! #[derive(ConfigGenerator, Default)]
76//! struct Config {
77//!   #[env_key = "NAME_KEY"]
78//!   pub name: String,
79//! }
80//!
81//! fn main() {
82//!   let config = Config::default()
83//!     .with_toml(&"tests/tomls/simple_config.toml")
84//!     .with_env();
85//! }
86//! ```
87//!
88//! Superimposing values follows certain rules:
89//! 1. If the original struct had an [Option<T>] field, the value for that field will _only_ be
90//! replaced if the new value is [Some]. A subseqently-loaded [None] value will _not_ be applied.
91//! 2. If the original struct had a [Vec<T>] field, subsequently-loaded values will be _combined_
92//! with prior values, such that the [Vec] has each new value pushed to the end.
93//! 3. For all other types, if a value is loaded, then that value will replace any previously-set value.
94
95pub use config_gen_macro_impl::ConfigGenerator;