Skip to main content

runi_core/
config.rs

1use std::collections::HashMap;
2use std::env;
3
4#[derive(Debug, Default)]
5pub struct Config {
6    values: HashMap<String, String>,
7}
8
9impl Config {
10    pub fn new() -> Self {
11        Self::default()
12    }
13
14    pub fn from_env(prefix: &str) -> Self {
15        let mut values = HashMap::new();
16        let prefix_upper = format!("{}_", prefix.to_uppercase());
17        for (key, value) in env::vars() {
18            if let Some(stripped) = key.strip_prefix(&prefix_upper) {
19                values.insert(stripped.to_lowercase(), value);
20            }
21        }
22        Self { values }
23    }
24
25    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
26        self.values.insert(key.into(), value.into());
27    }
28
29    pub fn get(&self, key: &str) -> Option<&str> {
30        self.values.get(key).map(|s| s.as_str())
31    }
32
33    pub fn get_or(&self, key: &str, default: &str) -> String {
34        self.get(key).unwrap_or(default).to_string()
35    }
36
37    pub fn get_u64(&self, key: &str) -> Option<u64> {
38        self.get(key).and_then(|v| v.parse().ok())
39    }
40
41    pub fn len(&self) -> usize {
42        self.values.len()
43    }
44
45    pub fn is_empty(&self) -> bool {
46        self.values.is_empty()
47    }
48
49    pub fn keys(&self) -> impl Iterator<Item = &str> {
50        self.values.keys().map(|s| s.as_str())
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use runi_test::prelude::*;
58    use runi_test::pretty_assertions::assert_eq;
59
60    #[fixture]
61    fn config() -> Config {
62        let mut c = Config::new();
63        c.set("host", "localhost");
64        c.set("port", "8080");
65        c.set("name", "runi");
66        c
67    }
68
69    #[test]
70    fn empty_config() {
71        let c = Config::new();
72        assert!(c.is_empty());
73        assert_eq!(c.get("missing"), None);
74    }
75
76    #[rstest]
77    fn get_values(config: Config) {
78        assert_eq!(config.get("host"), Some("localhost"));
79        assert_eq!(config.get("port"), Some("8080"));
80        assert_eq!(config.len(), 3);
81    }
82
83    #[rstest]
84    fn get_with_default(config: Config) {
85        assert_eq!(config.get_or("missing", "fallback"), "fallback");
86        assert_eq!(config.get_or("host", "fallback"), "localhost");
87    }
88
89    #[rstest]
90    #[case("port", Some(8080))]
91    #[case("name", None)]
92    #[case("missing", None)]
93    fn parse_u64(config: Config, #[case] key: &str, #[case] expected: Option<u64>) {
94        assert_eq!(config.get_u64(key), expected);
95    }
96
97    #[test]
98    fn from_env() {
99        unsafe {
100            env::set_var("RUNI_TEST_HOST", "localhost");
101            env::set_var("RUNI_TEST_PORT", "9090");
102        }
103        let c = Config::from_env("RUNI_TEST");
104        assert_eq!(c.get("host"), Some("localhost"));
105        assert_eq!(c.get("port"), Some("9090"));
106        unsafe {
107            env::remove_var("RUNI_TEST_HOST");
108            env::remove_var("RUNI_TEST_PORT");
109        }
110    }
111}