Skip to main content

cfg_rs/
prelude.rs

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