1use std::path::Path;
2
3#[derive(Debug, Clone, PartialEq)]
5pub enum Environment {
6 Local,
8 Development,
10 Staging,
12 Production,
14 Testing,
16 Custom(String),
18}
19
20impl Environment {
21 pub fn detect() -> Self {
23 match std::env::var("APP_ENV").ok().as_deref() {
24 Some("production") => Self::Production,
25 Some("staging") => Self::Staging,
26 Some("development") => Self::Development,
27 Some("testing") => Self::Testing,
28 Some("local") | None => Self::Local,
29 Some(other) => Self::Custom(other.to_string()),
30 }
31 }
32
33 pub fn env_file_suffix(&self) -> Option<&str> {
35 match self {
36 Self::Local => Some("local"),
37 Self::Production => Some("production"),
38 Self::Staging => Some("staging"),
39 Self::Development => Some("development"),
40 Self::Testing => Some("testing"),
41 Self::Custom(name) => Some(name.as_str()),
42 }
43 }
44
45 pub fn is_production(&self) -> bool {
47 matches!(self, Self::Production)
48 }
49
50 pub fn is_development(&self) -> bool {
52 matches!(self, Self::Local | Self::Development)
53 }
54}
55
56impl std::fmt::Display for Environment {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::Local => write!(f, "local"),
60 Self::Development => write!(f, "development"),
61 Self::Staging => write!(f, "staging"),
62 Self::Production => write!(f, "production"),
63 Self::Testing => write!(f, "testing"),
64 Self::Custom(name) => write!(f, "{name}"),
65 }
66 }
67}
68
69pub fn load_dotenv(project_root: &Path) -> Environment {
78 let env = Environment::detect();
79
80 if let Some(suffix) = env.env_file_suffix() {
85 let path = project_root.join(format!(".env.{suffix}.local"));
86 let _ = dotenvy::from_path(&path);
87 }
88
89 if let Some(suffix) = env.env_file_suffix() {
91 let path = project_root.join(format!(".env.{suffix}"));
92 let _ = dotenvy::from_path(&path);
93 }
94
95 let _ = dotenvy::from_path(project_root.join(".env.local"));
97
98 let _ = dotenvy::from_path(project_root.join(".env"));
100
101 env
102}
103
104pub fn env<T: std::str::FromStr>(key: &str, default: T) -> T {
114 std::env::var(key)
115 .ok()
116 .and_then(|v| v.parse().ok())
117 .unwrap_or(default)
118}
119
120pub fn env_required<T: std::str::FromStr>(key: &str) -> T {
132 std::env::var(key)
133 .ok()
134 .and_then(|v| v.parse().ok())
135 .unwrap_or_else(|| panic!("Required environment variable {key} is not set or invalid"))
136}
137
138pub fn env_optional<T: std::str::FromStr>(key: &str) -> Option<T> {
147 std::env::var(key).ok().and_then(|v| v.parse().ok())
148}