use std::sync::Once;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AppMode {
Development,
#[default]
Production,
}
impl AppMode {
pub fn overlay_filename(&self) -> &'static str {
match self {
AppMode::Development => "appsettings.Development.json",
AppMode::Production => "appsettings.Production.json",
}
}
pub fn from_env() -> Self {
Self::from_env_var("APP_ENV")
}
pub fn from_env_var(var: &str) -> Self {
match std::env::var(var) {
Ok(raw) => match Self::parse(&raw) {
Some(mode) => mode,
None => {
warn_invalid_once(var, &raw);
AppMode::Production
}
},
Err(_) => {
warn_unset_once(var);
AppMode::Production
}
}
}
fn parse(raw: &str) -> Option<AppMode> {
match raw.trim().to_lowercase().as_str() {
"production" | "prod" => Some(AppMode::Production),
"development" | "dev" => Some(AppMode::Development),
_ => None,
}
}
}
fn warn_unset_once(var: &str) {
static WARNED: Once = Once::new();
WARNED.call_once(|| {
tracing::warn!(
"{var} is not set; defaulting to Production. \
Set APP_ENV=Development for local development."
);
});
}
fn warn_invalid_once(var: &str, raw: &str) {
static WARNED: Once = Once::new();
WARNED.call_once(|| {
tracing::warn!(
"Invalid {var}={raw:?}; expected Production/Prod or Development/Dev. \
Falling back to Production."
);
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_mode_is_production() {
assert_eq!(AppMode::default(), AppMode::Production);
}
#[test]
fn parse_accepts_known_values_case_insensitively() {
assert_eq!(AppMode::parse("Production"), Some(AppMode::Production));
assert_eq!(AppMode::parse("prod"), Some(AppMode::Production));
assert_eq!(AppMode::parse(" PROD "), Some(AppMode::Production));
assert_eq!(AppMode::parse("Development"), Some(AppMode::Development));
assert_eq!(AppMode::parse("DEV"), Some(AppMode::Development));
}
#[test]
fn parse_rejects_unknown_values() {
assert_eq!(AppMode::parse("staging"), None);
assert_eq!(AppMode::parse(""), None);
assert_eq!(AppMode::parse("1"), None);
}
#[test]
fn from_env_var_unset_falls_back_to_production() {
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_THAT_IS_NEVER_SET");
assert_eq!(mode, AppMode::Production);
}
#[test]
fn from_env_var_invalid_falls_back_to_production() {
std::env::set_var("WEBX_TEST_APP_ENV_INVALID", "staging");
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_INVALID");
std::env::remove_var("WEBX_TEST_APP_ENV_INVALID");
assert_eq!(mode, AppMode::Production);
}
#[test]
fn from_env_var_valid_value_is_honoured() {
std::env::set_var("WEBX_TEST_APP_ENV_PRODUCTION", "prod");
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_PRODUCTION");
std::env::remove_var("WEBX_TEST_APP_ENV_PRODUCTION");
assert_eq!(mode, AppMode::Production);
}
#[test]
fn from_env_var_explicit_development_opts_out() {
std::env::set_var("WEBX_TEST_APP_ENV_DEVELOPMENT", "dev");
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_DEVELOPMENT");
std::env::remove_var("WEBX_TEST_APP_ENV_DEVELOPMENT");
assert_eq!(mode, AppMode::Development);
}
}