runi-core 0.1.2

Runi core library — foundation types plus a feature-gated bundle that re-exports runi-log and runi-cli
Documentation
use std::collections::HashMap;
use std::env;

#[derive(Debug, Default)]
pub struct Config {
    values: HashMap<String, String>,
}

impl Config {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn from_env(prefix: &str) -> Self {
        let mut values = HashMap::new();
        let prefix_upper = format!("{}_", prefix.to_uppercase());
        for (key, value) in env::vars() {
            if let Some(stripped) = key.strip_prefix(&prefix_upper) {
                values.insert(stripped.to_lowercase(), value);
            }
        }
        Self { values }
    }

    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.values.insert(key.into(), value.into());
    }

    pub fn get(&self, key: &str) -> Option<&str> {
        self.values.get(key).map(|s| s.as_str())
    }

    pub fn get_or(&self, key: &str, default: &str) -> String {
        self.get(key).unwrap_or(default).to_string()
    }

    pub fn get_u64(&self, key: &str) -> Option<u64> {
        self.get(key).and_then(|v| v.parse().ok())
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }

    pub fn is_empty(&self) -> bool {
        self.values.is_empty()
    }

    pub fn keys(&self) -> impl Iterator<Item = &str> {
        self.values.keys().map(|s| s.as_str())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use runi_test::prelude::*;
    use runi_test::pretty_assertions::assert_eq;

    #[fixture]
    fn config() -> Config {
        let mut c = Config::new();
        c.set("host", "localhost");
        c.set("port", "8080");
        c.set("name", "runi");
        c
    }

    #[test]
    fn empty_config() {
        let c = Config::new();
        assert!(c.is_empty());
        assert_eq!(c.get("missing"), None);
    }

    #[rstest]
    fn get_values(config: Config) {
        assert_eq!(config.get("host"), Some("localhost"));
        assert_eq!(config.get("port"), Some("8080"));
        assert_eq!(config.len(), 3);
    }

    #[rstest]
    fn get_with_default(config: Config) {
        assert_eq!(config.get_or("missing", "fallback"), "fallback");
        assert_eq!(config.get_or("host", "fallback"), "localhost");
    }

    #[rstest]
    #[case("port", Some(8080))]
    #[case("name", None)]
    #[case("missing", None)]
    fn parse_u64(config: Config, #[case] key: &str, #[case] expected: Option<u64>) {
        assert_eq!(config.get_u64(key), expected);
    }

    #[test]
    fn from_env() {
        unsafe {
            env::set_var("RUNI_TEST_HOST", "localhost");
            env::set_var("RUNI_TEST_PORT", "9090");
        }
        let c = Config::from_env("RUNI_TEST");
        assert_eq!(c.get("host"), Some("localhost"));
        assert_eq!(c.get("port"), Some("9090"));
        unsafe {
            env::remove_var("RUNI_TEST_HOST");
            env::remove_var("RUNI_TEST_PORT");
        }
    }
}