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
//! # Overview
//!
//! The [`Config`](self::derive::Config) derive macro simplifies application configuration by automatically loading
//! settings from various sources in the following order:
//! 1. **Environment Variables**.
//! 2. **Configuration File** (e.g., `toml`, `json`, `yaml`, `ini`, `ron`, `json5`).
//! 3. **Default Values**.
//!
//! ## Key Features
//!
//! - **Simplicity**: Minimal boilerplate. Define your configuration struct, customize the macro, and you're good to go.
//! - **Flexibility**: Supports a variety of configuration file formats including `toml`, `json`, `yaml`, `ini`, `ron`, and `json5`.
//! - **Integration**: Synergy with other crates, such as [`smart_default`](https://docs.rs/smart_default/latest/smart_default/).
//!
//! There are also several useful helper attributes for customizing the behavior of the derive macro.
//!
//! | Attribute | Functionality |
//! |---------------|--------------------------------------------------------------------------------------------------------------------------------------------|
//! | `prefix` | Sets a prefix for environment variables. Can be applied at the struct or field level. |
//! | `path` | Specifies the static path to a configuration file. The file extension may (though probably shouldn't) be omitted. |
//! | `env_path` | Resolves an environment variable at runtime to determine the configuration file path. |
//! | `default_path`| Specifies a fallback path used if the path determined by `env_path` does not exist. |
//! | `key` | Overrides the default environment variable name. This ignores the prefix and uses the provided key directly. |
//! | `name` | forwards to `#[serde(rename = "_")]` to rename fields during serialization/deserialization. It does not affect environment variable names. |
//! | `nest` | Required for non-standard types which must also derive [`Config`](self::derive::Config), used for nesting configuration structs. |
//! | `skip` | Skips loading the attribute from an environment variable. Necessary for types that don't implement [`FromStr`](std::str::FromStr) but are present in the configuration file. |
//! | `separator` | Specifies a character to separate the prefix and the field name. The default separator is "_". |
//!
//! ## Path Attribute Behavior
//!
//! - **`env_path`**: Resolves the provided environment variable into configuration filepath. This
//! takes precedence over `path` and `default_path`, but will not panic if the file or environment
//! does not exist.
//!
//! - **`path`**: Directly sets the path to the configuration file. When set, `default_path` may not be used. Panics if the file does not exist.
//!
//! - **`default_path`**: Identical to `path`, but does not panic if the file does not exist.
//!
//! ## Usage
//!
//! <br/>
//!
//! [`serde`](https://docs.rs/serde) is a required dependency.
//!
//! ```toml
//! [dependencies]
//! confgr = "0.2.0"
//! serde = { version = "1.0", features = ["derive"] }
//! ```
//!
//! Then define your configuration like so:
//!
//! ```rust
//! use confgr::prelude::*;
//!
//! # use std::fs::File;
//! # use std::io::Write;
//! # let mut file = std::fs::File::create("docs.toml").unwrap();
//! # writeln!(file, "address = '127.0.0.1'");
//! #[derive(Config)]
//! #[config(path = "docs.toml", prefix = "APP")]
//! pub struct AppConfig {
//! port: u32,
//! address: String,
//! #[config(key = "DEBUG_MODE")]
//! debug: bool,
//! }
//!
//! // Default implementation is required.
//! impl Default for AppConfig {
//! fn default() -> Self {
//! Self {
//! port: 3000,
//! address: "127.0.0.1".to_string(),
//! debug: false
//! }
//! }
//! }
//!
//! std::env::set_var("APP_PORT", "4000");
//! std::env::set_var("DEBUG_MODE", "true");
//!
//! let settings = AppConfig::load_config();
//!
//! # std::fs::remove_file("docs.toml").unwrap();
//!
//! assert_eq!(settings.port, 4000);
//! assert_eq!(settings.address, "127.0.0.1");
//! assert!(settings.debug)
//! ```
//!
//! ## Warnings/Pitfalls
//!
//! - Nested structs do not load separate files based on their own `path` attributes. If
//! you would like multiple files to be loaded, you must use multiple structs with multiple
//! [`load_config()`](core::Confgr::load_config()) calls. This may change in a future version.
//! - Types that do not implement [`FromStr`](std::str::FromStr) must use `#[config(skip)]` or `#[config(nest)]`.
//! - The `separator` character is only inserted between the prefix and the field name, not in any
//! part of the parsed field name.
//! - The `prefix` is applied per field or for the entire struct, but is ignored if `#[config(key = "_")]` is used.
//! - All configuration structs must implement [`Default`].
//! - Types used in configuration structs must implement [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html), [`Clone`], [`Debug`] and [`Default`].
//! - [`Option`] is not currently compatible with `#[config(nest)]` on types that implement [`Confgr`](self::core::Confgr).
//!
//! ## Debugging
//!
//! When encountering issues using the macro, the following methods may be of use.
//!
//! ### Verifying Environment Variables
//!
//! The [`get_env_keys()`](core::Confgr::get_env_keys) method can be used to retrieve the
//! resolved environment variable keys based on the struct's configuration.
//!
//! ```rust
//! use std::collections::HashMap;
//! use confgr::prelude::*;
//!
//! #[derive(Config, Default)]
//! #[config(prefix = "APP")]
//! pub struct AppConfig {
//! port: u32,
//! #[config(separator = "__")]
//! address: String,
//! #[config(key = "DEBUG_MODE")]
//! debug: bool,
//! }
//!
//! let keys: HashMap<String, String> = AppConfig::get_env_keys();
//!
//! assert_eq!(keys["port"], "APP_PORT");
//! assert_eq!(keys["address"], "APP__ADDRESS");
//! assert_eq!(keys["debug"], "DEBUG_MODE");
//! ```
//!
//! ### Verifying Configuration File Path
//!
//! You can use [`check_file()`](core::Confgr::check_file) to ensure that the configuration file
//! is accessible the path specified or resolved in the `path`, or `env_path` attribute.
//!
//! ```rust
//! use confgr::prelude::*;
//!
//! # use std::fs::File;
//! # use std::io::Write;
//! # let mut file = std::fs::File::create("env_config.toml").unwrap();
//! # writeln!(file, "port = 3000\ndebug = false");
//! # let mut file = std::fs::File::create("docs.toml").unwrap();
//! # writeln!(file, "port = 3000\ndebug = false");
//! #[derive(Config, Default)]
//! #[config(path = "docs.toml", env_path = "APP_CONFIG_FILE")]
//! pub struct AppConfig {
//! port: u32,
//! debug: bool,
//! }
//!
//! std::env::set_var("APP_CONFIG_FILE", "env_config.toml");
//! AppConfig::check_file().expect("Failed to open configuration file.");
//!
//! std::env::remove_var("APP_CONFIG_FILE");
//! AppConfig::check_file().expect("Failed to open configuration file.");
//!
//! # std::fs::remove_file("docs.toml").unwrap();
//! # std::fs::remove_file("env_config.toml").unwrap();
//! ```
//!
//! ### Test Deserialization
//!
//! The [`deserialize_from_file()`](core::Confgr::deserialize_from_file()) method can be used to manually test the config deserialization step. This
//! will give you the parsed configuration struct before default values are applied.
//!
//! ```rust
//! use confgr::prelude::*;
//!
//! # use std::fs::File;
//! # use std::io::Write;
//! # let mut file = std::fs::File::create("docs.toml").unwrap();
//! # writeln!(file, "port = 3000\ndebug = false");
//! #[derive(Config, Default)]
//! #[config(path = "docs.toml")]
//! pub struct AppConfig {
//! port: u32,
//! debug: bool,
//! }
//!
//! let config = AppConfig::deserialize_from_file().expect("Failed to deserialize configuration.");
//! println!("Deserialized configuration: {:?}", config);
//!
//! # std::fs::remove_file("docs.toml").unwrap();
//! ```
/// Traits and types consumed by the [`Config`](confgr_derive::Config) derive macro. Re-export from [`confgr_core`].
/// Derive macro for the [`Confgr`](confgr_core::Confgr) trait. Re-export from [`confgr_derive`].
/// Macro and trait exports for convenience.