use crate::{ZoeyError, Result};
use std::env;
use std::path::Path;
pub fn load_env() -> Result<()> {
match dotenvy::dotenv() {
Ok(path) => {
tracing::info!("✓ Loaded environment from: {}", path.display());
Ok(())
}
Err(dotenvy::Error::LineParse(line, pos)) => Err(ZoeyError::config(format!(
"Failed to parse .env file at line {}, position {}",
line, pos
))),
Err(dotenvy::Error::Io(_)) => {
tracing::warn!("No .env file found - using system environment variables only");
Ok(())
}
Err(e) => Err(ZoeyError::config(format!(
"Failed to load .env file: {}",
e
))),
}
}
pub fn load_env_from_path<P: AsRef<Path>>(path: P) -> Result<()> {
match dotenvy::from_path(path.as_ref()) {
Ok(_) => {
tracing::info!("✓ Loaded environment from: {}", path.as_ref().display());
Ok(())
}
Err(e) => Err(ZoeyError::config(format!(
"Failed to load {} environment file: {}",
path.as_ref().display(),
e
))),
}
}
pub fn get_required_env(key: &str) -> Result<String> {
env::var(key).map_err(|_| {
ZoeyError::config(format!(
"Required environment variable '{}' is not set. \
Check your .env file or system environment.",
key
))
})
}
pub fn get_env_or(key: &str, default: &str) -> String {
env::var(key).unwrap_or_else(|_| default.to_string())
}
pub fn get_env_bool(key: &str, default: bool) -> bool {
env::var(key)
.ok()
.and_then(|v| match v.to_lowercase().as_str() {
"true" | "1" | "yes" | "on" => Some(true),
"false" | "0" | "no" | "off" => Some(false),
_ => None,
})
.unwrap_or(default)
}
pub fn get_env_int<T>(key: &str, default: T) -> T
where
T: std::str::FromStr,
{
env::var(key)
.ok()
.and_then(|v| v.parse::<T>().ok())
.unwrap_or(default)
}
pub fn get_env_float(key: &str, default: f32) -> f32 {
env::var(key)
.ok()
.and_then(|v| v.parse::<f32>().ok())
.unwrap_or(default)
}
pub fn validate_env(required_vars: &[&str]) -> Result<()> {
let mut missing = Vec::new();
for var in required_vars {
if env::var(var).is_err() {
missing.push(*var);
}
}
if !missing.is_empty() {
return Err(ZoeyError::config(format!(
"Missing required environment variables: {}\n\
Run 'cargo run --bin generate-config' to create a .env file",
missing.join(", ")
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_env_bool() {
env::set_var("TEST_BOOL_TRUE", "true");
env::set_var("TEST_BOOL_FALSE", "false");
env::set_var("TEST_BOOL_1", "1");
env::set_var("TEST_BOOL_0", "0");
assert_eq!(get_env_bool("TEST_BOOL_TRUE", false), true);
assert_eq!(get_env_bool("TEST_BOOL_FALSE", true), false);
assert_eq!(get_env_bool("TEST_BOOL_1", false), true);
assert_eq!(get_env_bool("TEST_BOOL_0", true), false);
assert_eq!(get_env_bool("NONEXISTENT", true), true);
assert_eq!(get_env_bool("NONEXISTENT", false), false);
env::remove_var("TEST_BOOL_TRUE");
env::remove_var("TEST_BOOL_FALSE");
env::remove_var("TEST_BOOL_1");
env::remove_var("TEST_BOOL_0");
}
#[test]
fn test_get_env_int() {
env::set_var("TEST_INT", "42");
assert_eq!(get_env_int("TEST_INT", 0), 42);
assert_eq!(get_env_int("NONEXISTENT", 99), 99);
env::remove_var("TEST_INT");
}
#[test]
fn test_get_env_float() {
env::set_var("TEST_FLOAT", "0.7");
assert_eq!(get_env_float("TEST_FLOAT", 0.0), 0.7);
assert_eq!(get_env_float("NONEXISTENT", 1.5), 1.5);
env::remove_var("TEST_FLOAT");
}
#[test]
fn test_get_env_or() {
env::set_var("TEST_STRING", "hello");
assert_eq!(get_env_or("TEST_STRING", "default"), "hello");
assert_eq!(get_env_or("NONEXISTENT", "default"), "default");
env::remove_var("TEST_STRING");
}
}