tracel_xtask/
environment.rs

1use strum::{Display, EnumIter, EnumString};
2
3use crate::group_info;
4
5#[derive(EnumString, EnumIter, Default, Display, Clone, PartialEq, clap::ValueEnum)]
6#[strum(serialize_all = "lowercase")]
7pub enum Environment {
8    /// Development environment (alias: dev).
9    #[default]
10    #[strum(serialize = "dev")]
11    #[clap(alias = "dev")]
12    Development,
13    /// Staging environment (alias: stag).
14    #[strum(serialize = "stag")]
15    #[clap(alias = "stag")]
16    Staging,
17    /// Testing environment (alias: test).
18    #[strum(serialize = "test")]
19    #[clap(alias = "test")]
20    Test,
21    /// Production environment (alias: prod).
22    #[strum(serialize = "prod")]
23    #[clap(alias = "prod")]
24    Production,
25}
26
27impl Environment {
28    pub fn get_dotenv_filename(&self) -> String {
29        format!(".env.{self}")
30    }
31
32    pub fn get_dotenv_secrets_filename(&self) -> String {
33        format!("{}.secrets", self.get_dotenv_filename())
34    }
35
36    pub(crate) fn load(&self) -> anyhow::Result<()> {
37        let filename = self.get_dotenv_filename();
38        let secrets_filename = self.get_dotenv_secrets_filename();
39        if dotenvy::from_filename(".env").is_ok() {
40            group_info!("loading '.env' file...");
41        }
42        if dotenvy::from_filename(&filename).is_ok() {
43            group_info!("loading {filename} file...");
44        }
45        if dotenvy::from_filename(&secrets_filename).is_ok() {
46            group_info!("loading {secrets_filename} file...");
47        }
48        Ok(())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use rstest::rstest;
56    use serial_test::serial;
57    use std::env;
58
59    fn expected_vars(env: &Environment) -> Vec<(String, String)> {
60        let suffix = match env {
61            Environment::Development => "DEV",
62            Environment::Staging => "STAG",
63            Environment::Test => "TEST",
64            Environment::Production => "PROD",
65        };
66
67        vec![
68            ("FROM_DOTENV".to_string(), ".env".to_string()),
69            (
70                format!("FROM_DOTENV_{suffix}").to_string(),
71                env.get_dotenv_filename(),
72            ),
73            (
74                format!("FROM_DOTENV_{suffix}_SECRETS").to_string(),
75                env.get_dotenv_secrets_filename(),
76            ),
77        ]
78    }
79
80    #[rstest]
81    #[case::dev(Environment::Development)]
82    #[case::stag(Environment::Staging)]
83    #[case::test(Environment::Test)]
84    #[case::prod(Environment::Production)]
85    #[serial]
86    fn test_environment_load(#[case] env: Environment) {
87        // Remove possible prior values
88        for (key, _) in expected_vars(&env) {
89            env::remove_var(key);
90        }
91
92        // Run the actual function under test
93        env.load().expect("Environment load should succeed");
94
95        // Assert each expected env var is present and has the correct value
96        for (key, expected_value) in expected_vars(&env) {
97            let actual_value =
98                env::var(&key).unwrap_or_else(|_| panic!("Missing expected env var: {key}"));
99            assert_eq!(
100                actual_value, expected_value,
101                "Environment variable {key} should be set to {expected_value} but was {actual_value}"
102            );
103        }
104    }
105}