tracel_xtask/
environment.rs1use 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 #[default]
10 #[strum(serialize = "dev")]
11 #[clap(alias = "dev")]
12 Development,
13 #[strum(serialize = "stag")]
15 #[clap(alias = "stag")]
16 Staging,
17 #[strum(serialize = "test")]
19 #[clap(alias = "test")]
20 Test,
21 #[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 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 for (key, _) in expected_vars(&env) {
111 env::remove_var(key);
112 }
113
114 env.load(Some("../.."))
116 .expect("Environment load should succeed");
117
118 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}