tracel_xtask/
environment.rs

1use strum::{Display, EnumIter, EnumString};
2
3use crate::{group_error, 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 fn get_env_files(&self) -> [String; 3] {
37        let filename = self.get_dotenv_filename();
38        let secrets_filename = self.get_dotenv_secrets_filename();
39        [
40            ".env".to_owned(),
41            filename.to_owned(),
42            secrets_filename.to_owned(),
43        ]
44    }
45
46    /// Load the .env environment files family.
47    /// You don't need to call it in an xtask binary but can be useful
48    /// in a non-xtask binary.
49    pub fn load(&self, prefix: Option<&str>) -> anyhow::Result<()> {
50        let files = self.get_env_files();
51        files.iter().for_each(|f| {
52            let path = if let Some(p) = prefix {
53                std::path::PathBuf::from(p).join(f)
54            } else {
55                std::path::PathBuf::from(f)
56            };
57            if path.exists() {
58                match dotenvy::from_filename(f) {
59                    Ok(_) => {
60                        group_info!("loading '{}' file...", f);
61                    }
62                    Err(e) => {
63                        group_error!("error while loading '{}' file ({})", f, e);
64                    }
65                }
66            } else {
67                group_info!("environment file '{}' does not exist, skipping...", f);
68            }
69        });
70        Ok(())
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use rstest::rstest;
78    use serial_test::serial;
79    use std::env;
80
81    fn expected_vars(env: &Environment) -> Vec<(String, String)> {
82        let suffix = match env {
83            Environment::Development => "DEV",
84            Environment::Staging => "STAG",
85            Environment::Test => "TEST",
86            Environment::Production => "PROD",
87        };
88
89        vec![
90            ("FROM_DOTENV".to_string(), ".env".to_string()),
91            (
92                format!("FROM_DOTENV_{suffix}").to_string(),
93                env.get_dotenv_filename(),
94            ),
95            (
96                format!("FROM_DOTENV_{suffix}_SECRETS").to_string(),
97                env.get_dotenv_secrets_filename(),
98            ),
99        ]
100    }
101
102    #[rstest]
103    #[case::dev(Environment::Development)]
104    #[case::stag(Environment::Staging)]
105    #[case::test(Environment::Test)]
106    #[case::prod(Environment::Production)]
107    #[serial]
108    fn test_environment_load(#[case] env: Environment) {
109        // Remove possible prior values
110        for (key, _) in expected_vars(&env) {
111            env::remove_var(key);
112        }
113
114        // Run the actual function under test
115        env.load(Some("../.."))
116            .expect("Environment load should succeed");
117
118        // Assert each expected env var is present and has the correct value
119        for (key, expected_value) in expected_vars(&env) {
120            let actual_value =
121                env::var(&key).unwrap_or_else(|_| panic!("Missing expected env var: {key}"));
122            assert_eq!(
123                actual_value, expected_value,
124                "Environment variable {key} should be set to {expected_value} but was {actual_value}"
125            );
126        }
127    }
128}