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}