use std::path::Path;
#[derive(Debug, Clone, PartialEq)]
pub enum Environment {
Local,
Development,
Staging,
Production,
Testing,
Custom(String),
}
impl Environment {
pub fn detect() -> Self {
match std::env::var("APP_ENV").ok().as_deref() {
Some("production") => Self::Production,
Some("staging") => Self::Staging,
Some("development") => Self::Development,
Some("testing") => Self::Testing,
Some("local") | None => Self::Local,
Some(other) => Self::Custom(other.to_string()),
}
}
pub fn env_file_suffix(&self) -> Option<&str> {
match self {
Self::Local => Some("local"),
Self::Production => Some("production"),
Self::Staging => Some("staging"),
Self::Development => Some("development"),
Self::Testing => Some("testing"),
Self::Custom(name) => Some(name.as_str()),
}
}
pub fn is_production(&self) -> bool {
matches!(self, Self::Production)
}
pub fn is_development(&self) -> bool {
matches!(self, Self::Local | Self::Development)
}
}
impl std::fmt::Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Local => write!(f, "local"),
Self::Development => write!(f, "development"),
Self::Staging => write!(f, "staging"),
Self::Production => write!(f, "production"),
Self::Testing => write!(f, "testing"),
Self::Custom(name) => write!(f, "{name}"),
}
}
}
pub fn load_dotenv(project_root: &Path) -> Environment {
let env = Environment::detect();
if let Some(suffix) = env.env_file_suffix() {
let path = project_root.join(format!(".env.{suffix}.local"));
let _ = dotenvy::from_path(&path);
}
if let Some(suffix) = env.env_file_suffix() {
let path = project_root.join(format!(".env.{suffix}"));
let _ = dotenvy::from_path(&path);
}
let _ = dotenvy::from_path(project_root.join(".env.local"));
let _ = dotenvy::from_path(project_root.join(".env"));
env
}
pub fn env<T: std::str::FromStr>(key: &str, default: T) -> T {
std::env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
pub fn env_required<T: std::str::FromStr>(key: &str) -> T {
std::env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or_else(|| panic!("Required environment variable {key} is not set or invalid"))
}
pub fn env_optional<T: std::str::FromStr>(key: &str) -> Option<T> {
std::env::var(key).ok().and_then(|v| v.parse().ok())
}