geneos_toolkit/
env.rs

1use std::env;
2use std::error::Error;
3use std::fmt;
4
5#[derive(Debug)]
6pub enum EnvError {
7    VarError(env::VarError),
8    IoError(std::io::Error),
9    MissingSecureEnvSupport,
10    #[cfg(feature = "secure-env")]
11    DecryptionFailed(String),
12    #[cfg(feature = "secure-env")]
13    MissingKeyFile,
14    #[cfg(feature = "secure-env")]
15    KeyFileFormatError(String),
16}
17
18impl From<env::VarError> for EnvError {
19    fn from(err: env::VarError) -> Self {
20        EnvError::VarError(err)
21    }
22}
23
24impl From<std::io::Error> for EnvError {
25    fn from(err: std::io::Error) -> Self {
26        EnvError::IoError(err)
27    }
28}
29
30impl fmt::Display for EnvError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            EnvError::VarError(e) => write!(f, "Environment variable error: {}", e),
34            EnvError::IoError(e) => write!(f, "IO error: {}", e),
35            EnvError::MissingSecureEnvSupport => {
36                write!(
37                    f,
38                    "Secure environment support is disabled (enable the 'secure-env' feature)"
39                )
40            }
41            #[cfg(feature = "secure-env")]
42            EnvError::DecryptionFailed(msg) => write!(f, "Failed to decrypt: {}", msg),
43            #[cfg(feature = "secure-env")]
44            EnvError::MissingKeyFile => write!(f, "Missing key file for decryption"),
45            #[cfg(feature = "secure-env")]
46            EnvError::KeyFileFormatError(msg) => write!(f, "Key file format error: {}", msg),
47        }
48    }
49}
50
51impl Error for EnvError {
52    fn source(&self) -> Option<&(dyn Error + 'static)> {
53        match self {
54            EnvError::VarError(e) => Some(e),
55            EnvError::IoError(e) => Some(e),
56            _ => None,
57        }
58    }
59}
60
61/// Retrieves an environment variable's value.
62/// Returns `MissingSecureEnvSupport` if the value is encrypted and `secure-env` is disabled.
63///
64/// # Example (ignored to avoid mutating process env in doctest)
65/// ```ignore
66/// use geneos_toolkit::env::get_var;
67/// std::env::set_var("PLAIN_EXAMPLE", "ok");
68/// assert_eq!(get_var("PLAIN_EXAMPLE").unwrap(), "ok");
69/// ```
70pub fn get_var(name: &str) -> Result<String, EnvError> {
71    let val = env::var(name)?;
72    #[cfg(not(feature = "secure-env"))]
73    if is_encrypted(&val) {
74        return Err(EnvError::MissingSecureEnvSupport);
75    }
76    Ok(val)
77}
78
79/// Retrieves an environment variable's value or returns a default if not set.
80/// Returns an error if the value is encrypted and secure support is disabled.
81///
82/// # Example (ignored to avoid mutating process env in doctest)
83/// ```ignore
84/// use geneos_toolkit::env::get_var_or;
85/// assert_eq!(get_var_or("MISSING", "fallback").unwrap(), "fallback");
86/// ```
87pub fn get_var_or(name: &str, default: &str) -> Result<String, EnvError> {
88    match env::var(name) {
89        Ok(val) => {
90            #[cfg(not(feature = "secure-env"))]
91            if is_encrypted(&val) {
92                return Err(EnvError::MissingSecureEnvSupport);
93            }
94            Ok(val)
95        }
96        Err(env::VarError::NotPresent) => Ok(default.to_string()),
97        Err(e) => Err(EnvError::VarError(e)),
98    }
99}
100
101/// Checks if a string slice is encrypted. Encrypted values start with "+encs+".
102pub fn is_encrypted(value: &str) -> bool {
103    value.starts_with("+encs+")
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use temp_env::with_var;
110
111    #[test]
112    fn test_get_env() {
113        with_var("TEST_VAR", Some("test_value"), || {
114            assert_eq!(get_var("TEST_VAR").unwrap(), "test_value");
115            assert_eq!(get_var_or("TEST_VAR", "default").unwrap(), "test_value");
116        });
117
118        with_var::<_, &str, _, _>("NON_EXISTENT_VAR", None, || {
119            assert!(get_var("NON_EXISTENT_VAR").is_err());
120            assert_eq!(
121                get_var_or("NON_EXISTENT_VAR", "default").unwrap(),
122                "default"
123            );
124        });
125    }
126
127    #[test]
128    fn test_is_encrypted() {
129        assert!(is_encrypted("+encs+1234567890ABCDEF"));
130        assert!(!is_encrypted("plain_text"));
131        assert!(!is_encrypted(""));
132    }
133}