cfg_rs/prelude.rs
1use std::borrow::Borrow;
2
3use crate::{ConfigError, ConfigValue, Configuration, FromConfig};
4
5/// Macro to generate config instance from a map of key-value pairs.
6/// The keys in the map are config keys, e.g. "port".
7/// The values in the map are string values, e.g. "8080".
8/// This macro will panic if any required config is missing or
9/// if any config value cannot be parsed into the expected type.
10/// # Example
11/// ```rust
12/// use cfg_rs::*;
13/// #[derive(Debug, FromConfig)]
14/// struct AppConfig {
15/// port: u16,
16/// host: String,
17/// }
18/// let config: AppConfig = from_static_map!(AppConfig, {
19/// "port" => "8080",
20/// "host" => "localhost",
21/// });
22/// assert_eq!(config.port, 8080);
23/// assert_eq!(config.host, "localhost");
24/// ```
25/// Note: This macro is intended for use in tests or examples where
26/// you want to quickly create a config instance from inline key-value pairs.
27/// It is not recommended for use in production code.
28#[macro_export]
29macro_rules! from_static_map {
30 ( $ty:ty, { $( $key:expr => $value:expr ),* $(,)? } ) => {{
31 use $crate::*;
32 use std::collections::HashMap;
33 let mut config: HashMap<String, String> = HashMap::new();
34 $(
35 config.insert($key.to_string(), $value.to_string());
36 )*
37 from_map::<$ty, _, _, _>(config, "").expect("from_static_map failed")
38 }};
39}
40
41/// Generate config instance from a map of key-value pairs.
42/// The keys in the map are full config keys, e.g. "cfg.app.port".
43/// The values in the map are string values, e.g. "8080".
44/// The `prefix` is used to scope the config keys, e.g. "cfg.app".
45/// This function will return an error if any required config is missing or
46/// if any config value cannot be parsed into the expected type.
47/// # Example
48/// ```rust
49/// use std::collections::HashMap;
50/// use cfg_rs::*;
51/// #[derive(Debug, FromConfig)]
52/// struct AppConfig {
53/// port: u16,
54/// host: String,
55/// }
56/// let mut map = HashMap::new();
57/// map.insert("cfg.app.port", "8080");
58/// map.insert("cfg.app.host", "localhost");
59/// let config: AppConfig = from_map(map, "cfg.app").unwrap();
60/// assert_eq!(config.port, 8080);
61/// assert_eq!(config.host, "localhost");
62/// ```
63#[allow(unused_mut)]
64pub fn from_map<
65 T: FromConfig,
66 I: IntoIterator<Item = (K, V)>,
67 K: Borrow<str>,
68 V: Into<ConfigValue<'static>>,
69>(
70 map: I,
71 prefix: &str,
72) -> Result<T, ConfigError> {
73 let mut config = Configuration::new().register_kv("default");
74 for (k, v) in map {
75 config = config.set(k, v);
76 }
77 let mut config = config.finish()?;
78 #[cfg(feature = "rand")]
79 {
80 config = config.register_random()?;
81 }
82 config.get(prefix)
83}
84
85/// Generate config instance from environment variables.
86/// The `prefix` is used to scope the config keys, e.g. "CFG_APP".
87/// This function will return an error if any required config is missing or
88/// if any config value cannot be parsed into the expected type.
89/// # Example
90/// ```rust
91/// use cfg_rs::*;
92/// #[derive(Debug, FromConfig)]
93/// struct AppConfig {
94/// port: u16,
95/// host: String,
96/// }
97/// std::env::set_var("CFG_APP_PORT", "8080");
98/// std::env::set_var("CFG_APP_HOST", "localhost");
99/// let config: AppConfig = from_env("CFG_APP").unwrap();
100/// assert_eq!(config.port, 8080);
101/// assert_eq!(config.host, "localhost");
102/// ```
103#[allow(unused_mut)]
104pub fn from_env<T: FromConfig>(prefix: &str) -> Result<T, ConfigError> {
105 let mut config = Configuration::new().register_prefix_env(prefix)?;
106 #[cfg(feature = "rand")]
107 {
108 config = config.register_random()?;
109 }
110 config.get("")
111}
112
113#[cfg_attr(coverage_nightly, coverage(off))]
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::FromConfig;
118 use std::collections::HashMap;
119
120 #[derive(Debug, PartialEq, FromConfig)]
121 #[config(crate = "crate")]
122 struct TestApp {
123 port: u16,
124 host: String,
125 }
126
127 #[test]
128 fn test_from_map_happy_path() {
129 let mut map = HashMap::new();
130 map.insert("cfg.app.port", "8080");
131 map.insert("cfg.app.host", "localhost");
132
133 let cfg: TestApp = from_map(map, "cfg.app").expect("from_map failed");
134 assert_eq!(
135 cfg,
136 TestApp {
137 port: 8080,
138 host: "localhost".to_string()
139 }
140 );
141 }
142
143 #[test]
144 fn test_from_env_happy_path() {
145 // Use a unique prefix to avoid colliding with other env vars
146 let prefix = "TEST_APP";
147 std::env::set_var("TEST_APP_PORT", "9090");
148 std::env::set_var("TEST_APP_HOST", "127.0.0.1");
149
150 let cfg: TestApp = from_env(prefix).expect("from_env failed");
151 assert_eq!(
152 cfg,
153 TestApp {
154 port: 9090,
155 host: "127.0.0.1".to_string()
156 }
157 );
158
159 // Clean up
160 std::env::remove_var("TEST_APP_PORT");
161 std::env::remove_var("TEST_APP_HOST");
162 }
163
164 #[test]
165 fn test_load_from_map_macro_happy_path() {
166 // Use the macro to construct TestApp from inline kvs
167 let app: TestApp = from_static_map!(TestApp, {
168 "port" => "8080",
169 "host" => "localhost",
170 });
171
172 assert_eq!(
173 app,
174 TestApp {
175 port: 8080,
176 host: "localhost".to_string()
177 }
178 );
179 }
180
181 #[test]
182 fn test_load_from_map_macro_single_entry() {
183 // Single entry form should also work
184 // host is missing so deriving FromConfig would error; instead test getting a struct with only port
185 #[derive(Debug, PartialEq, FromConfig)]
186 #[config(crate = "crate")]
187 struct OnlyPort {
188 port: u16,
189 }
190 let only: OnlyPort = from_static_map!(OnlyPort, { "port" => "7070" });
191 assert_eq!(only, OnlyPort { port: 7070 });
192 }
193}