Skip to main content

rskit_util/
env.rs

1//! Safe environment variable parsing with defaults.
2
3use std::str::FromStr;
4
5/// Read a string environment variable.
6///
7/// Returns `None` when the variable is not set or cannot be represented as
8/// Unicode.
9pub fn get(key: &str) -> Option<String> {
10    std::env::var(key).ok()
11}
12
13/// Read a string environment variable and ignore empty values.
14///
15/// Returns `None` when the variable is unset, non-Unicode, or set to an empty
16/// string.
17pub fn get_non_empty(key: &str) -> Option<String> {
18    get(key).filter(|value| !value.is_empty())
19}
20
21/// Read a string environment variable with a fallback value.
22///
23/// # Examples
24///
25/// ```
26/// use rskit_util::env::get_or;
27/// let val = get_or("NON_EXISTENT_KEY", "fallback");
28/// assert_eq!(val, "fallback");
29/// ```
30pub fn get_or(key: &str, fallback: impl Into<String>) -> String {
31    std::env::var(key).unwrap_or_else(|_| fallback.into())
32}
33
34/// Read and parse an environment variable into any type implementing `FromStr`.
35/// Returns `None` if the variable is not set or if parsing fails.
36///
37/// # Examples
38///
39/// ```
40/// use rskit_util::env::get_parsed;
41/// let port: Option<u16> = get_parsed("PORT");
42/// ```
43pub fn get_parsed<T>(key: &str) -> Option<T>
44where
45    T: FromStr,
46{
47    std::env::var(key)
48        .ok()
49        .and_then(|val| val.parse::<T>().ok())
50}
51
52/// Safely parse boolean variables.
53/// Recognizes "true", "1", "yes", "on" as true, and "false", "0", "no", "off" as false.
54/// If the variable is unset or unrecognized, returns the fallback value.
55///
56/// # Examples
57///
58/// ```
59/// use rskit_util::env::get_bool;
60/// let is_enabled = get_bool("FEATURE_FLAG", false);
61/// ```
62pub fn get_bool(key: &str, fallback: bool) -> bool {
63    let Ok(val) = std::env::var(key) else {
64        return fallback;
65    };
66    parse_bool_str(&val, fallback)
67}
68
69fn parse_bool_str(val: &str, fallback: bool) -> bool {
70    match val.trim().to_lowercase().as_str() {
71        "true" | "1" | "yes" | "on" => true,
72        "false" | "0" | "no" | "off" => false,
73        _ => fallback,
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_get_or() {
83        assert_eq!(
84            get_or("RSKIT_UTIL_TEST_NON_EXISTENT_VAR_12345", "foo"),
85            "foo"
86        );
87        // We know CARGO_MANIFEST_DIR is set by Cargo during tests.
88        assert!(!get_or("CARGO_MANIFEST_DIR", "").is_empty());
89    }
90
91    #[test]
92    fn test_get_non_empty() {
93        assert_eq!(
94            get_non_empty("RSKIT_UTIL_TEST_NON_EXISTENT_VAR_12345"),
95            None
96        );
97        assert!(get_non_empty("CARGO_MANIFEST_DIR").is_some());
98    }
99
100    #[test]
101    fn test_get_parsed() {
102        assert_eq!(
103            get_parsed::<u16>("RSKIT_UTIL_TEST_NON_EXISTENT_VAR_12345"),
104            None
105        );
106        // CARGO_PKG_VERSION_MAJOR is set by Cargo to a valid integer.
107        let expected: u16 = std::env::var("CARGO_PKG_VERSION_MAJOR")
108            .expect("CARGO_PKG_VERSION_MAJOR set by Cargo")
109            .parse()
110            .expect("CARGO_PKG_VERSION_MAJOR parses as u16");
111        assert_eq!(get_parsed::<u16>("CARGO_PKG_VERSION_MAJOR"), Some(expected));
112    }
113
114    #[test]
115    fn test_get_bool() {
116        assert!(get_bool("RSKIT_UTIL_TEST_NON_EXISTENT_VAR_12345", true));
117        assert!(!get_bool("RSKIT_UTIL_TEST_NON_EXISTENT_VAR_12345", false));
118    }
119
120    #[test]
121    fn test_parse_bool_str() {
122        assert!(parse_bool_str("true", false));
123        assert!(parse_bool_str("1", false));
124        assert!(parse_bool_str("yes", false));
125        assert!(parse_bool_str("on", false));
126
127        assert!(!parse_bool_str("false", true));
128        assert!(!parse_bool_str("0", true));
129        assert!(!parse_bool_str("no", true));
130        assert!(!parse_bool_str("off", true));
131
132        assert!(parse_bool_str("maybe", true));
133        assert!(!parse_bool_str("maybe", false));
134    }
135}