1use std::path::Path;
2
3#[derive(Debug, Clone, PartialEq)]
5pub enum Environment {
6 Local,
7 Development,
8 Staging,
9 Production,
10 Testing,
11 Custom(String),
12}
13
14impl Environment {
15 pub fn detect() -> Self {
17 match std::env::var("APP_ENV").ok().as_deref() {
18 Some("production") => Self::Production,
19 Some("staging") => Self::Staging,
20 Some("development") => Self::Development,
21 Some("testing") => Self::Testing,
22 Some("local") | None => Self::Local,
23 Some(other) => Self::Custom(other.to_string()),
24 }
25 }
26
27 pub fn env_file_suffix(&self) -> Option<&str> {
29 match self {
30 Self::Local => Some("local"),
31 Self::Production => Some("production"),
32 Self::Staging => Some("staging"),
33 Self::Development => Some("development"),
34 Self::Testing => Some("testing"),
35 Self::Custom(name) => Some(name.as_str()),
36 }
37 }
38
39 pub fn is_production(&self) -> bool {
41 matches!(self, Self::Production)
42 }
43
44 pub fn is_development(&self) -> bool {
46 matches!(self, Self::Local | Self::Development)
47 }
48}
49
50impl std::fmt::Display for Environment {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Self::Local => write!(f, "local"),
54 Self::Development => write!(f, "development"),
55 Self::Staging => write!(f, "staging"),
56 Self::Production => write!(f, "production"),
57 Self::Testing => write!(f, "testing"),
58 Self::Custom(name) => write!(f, "{name}"),
59 }
60 }
61}
62
63pub fn load_dotenv(project_root: &Path) -> Environment {
72 let env = Environment::detect();
73
74 if let Some(suffix) = env.env_file_suffix() {
79 let path = project_root.join(format!(".env.{suffix}.local"));
80 let _ = dotenvy::from_path(&path);
81 }
82
83 if let Some(suffix) = env.env_file_suffix() {
85 let path = project_root.join(format!(".env.{suffix}"));
86 let _ = dotenvy::from_path(&path);
87 }
88
89 let _ = dotenvy::from_path(project_root.join(".env.local"));
91
92 let _ = dotenvy::from_path(project_root.join(".env"));
94
95 env
96}
97
98pub fn env<T: std::str::FromStr>(key: &str, default: T) -> T {
108 std::env::var(key)
109 .ok()
110 .and_then(|v| v.parse().ok())
111 .unwrap_or(default)
112}
113
114pub fn env_required<T: std::str::FromStr>(key: &str) -> T {
126 std::env::var(key)
127 .ok()
128 .and_then(|v| v.parse().ok())
129 .unwrap_or_else(|| panic!("Required environment variable {key} is not set or invalid"))
130}
131
132pub fn env_optional<T: std::str::FromStr>(key: &str) -> Option<T> {
141 std::env::var(key).ok().and_then(|v| v.parse().ok())
142}