1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#![deny(missing_docs)]
#![doc(issue_tracker_base_url = "https://github.com/dariocurr/env-settings/issues")]
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/dariocurr/env-settings/main/docs/logo.svg",
    html_favicon_url = "https://raw.githubusercontent.com/dariocurr/env-settings/main/docs/logo.ico"
)]

//! # **Env Settinsg Utilss**

use std::{collections, env, error, fmt};

/// The result type provided by `EnvSettings`
pub type EnvSettingsResult<T> = Result<T, EnvSettingsError>;

/// The error that may occurs during `EnvSettings` resolution
#[derive(Debug)]
pub enum EnvSettingsError {
    /// Error raised when a convertion fails
    Convert(&'static str, String, &'static str),

    /// Error raised when environment variables resolution from a file fails
    File(String, Box<dyn error::Error>),

    /// Error raised when an environment variable not exists
    NotExists(&'static str),
}

impl fmt::Display for EnvSettingsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            EnvSettingsError::NotExists(env_variable) => {
                write!(f, "Environment variable named `{}` not found", env_variable)
            }
            EnvSettingsError::Convert(field_name, field_value, field_type) => write!(
                f,
                "Unable to convert the field `{}`: `{}` to `{}`",
                field_name, field_value, field_type
            ),
            EnvSettingsError::File(file_path, err) => write!(
                f,
                "Error occurs while reading `{}` as environment variable file: {}",
                file_path, err
            ),
        }
    }
}

/// Get the environment variables
pub fn get_env_variables(case_insensitive: bool) -> collections::HashMap<String, String> {
    let env_variables = env::vars();
    if case_insensitive {
        env_variables
            .map(|(key, value)| (key.to_lowercase(), value))
            .collect()
    } else {
        env_variables.collect()
    }
}

/// Load the environment variables file path
pub fn load_env_file_path(file_path: &str) -> EnvSettingsResult<()> {
    if let Err(err) = dotenvy::from_path(file_path) {
        Err(EnvSettingsError::File(file_path.to_string(), err.into()))
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use rstest::rstest;
    use std::io::prelude::Write;
    use std::{fs, path};

    #[rstest]
    #[case("KEY", "value", true, "key", Some("value"))]
    #[case("KEY", "value", true, "KEY", None)]
    #[case("KEY", "value", false, "key", None)]
    #[case("KEY", "value", false, "KEY", Some("value"))]
    fn test_get_env_variables(
        #[case] key: &str,
        #[case] value: &str,
        #[case] case_insensitive: bool,
        #[case] recover_key: &str,
        #[case] expected_result: Option<&str>,
    ) {
        env::set_var(key, value);
        let env_variables = get_env_variables(case_insensitive);
        let actual_result = env_variables.get(recover_key).map(|value| value.as_str());
        assert_eq!(actual_result, expected_result);
    }

    #[rstest]
    #[case("KEY", "value", Some("file_path"))]
    #[case("KEY", "value", None)]
    fn test_load_env_file_path(
        #[case] key: &str,
        #[case] value: &str,
        #[case] file_path: Option<&str>,
    ) {
        let (file_path, is_successful) = if let Some(file_path) = file_path {
            (file_path.to_string(), false)
        } else {
            let temp_dir: path::PathBuf = assert_fs::TempDir::new()
                .expect("Error occurs while creating the test temp directory!")
                .to_path_buf();
            fs::create_dir_all(&temp_dir)
                .expect("Error occurs while creating the test temp directory!");
            let temp_file_path = temp_dir.join("test_file");
            let pair = format!("{}={}\n", key, value);
            let mut temp_file = fs::File::create(&temp_file_path)
                .expect("Error occurs while creating the test temp file!");
            temp_file
                .write_all(pair.as_bytes())
                .expect("Error occurs while writing the test temp file!");
            (temp_file_path.to_string_lossy().to_string(), true)
        };
        let actual_result = load_env_file_path(&file_path);
        if is_successful {
            let actual_value = env::var(key).expect("Test environment variable not set!");
            assert_eq!(actual_value, value);
        } else {
            assert!(actual_result.is_err())
        }
    }
}