feat: added file loader

This commit is contained in:
darwincereska
2026-06-08 12:38:41 -04:00
parent 56339890e5
commit 36c74e0c25
8 changed files with 359 additions and 87 deletions
+153 -36
View File
@@ -1,43 +1,160 @@
#[cfg(test)]
mod test_main {
use envkit::*;
use envkit::{EnvLoader, error::EnvError};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
/// Tests that `get` successfully retrieves an existing environment variable.
fn test_get() {
let env = EnvLoader;
unsafe {
std::env::set_var("TEST_VAR", "test_value");
}
assert_eq!(env.get::<String>("TEST_VAR"), Ok("test_value".to_string()));
fn unique_key(name: &str) -> String {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time should be after the Unix epoch")
.as_nanos();
format!("ENVKIT_TEST_{name}_{nanos}")
}
#[test]
fn get_reads_required_typed_values() {
let env = EnvLoader::new();
let name_key = unique_key("NAME");
let port_key = unique_key("PORT");
let debug_key = unique_key("DEBUG");
let list_key = unique_key("PORTS");
unsafe {
std::env::set_var(&name_key, "envkit");
std::env::set_var(&port_key, "3000");
std::env::set_var(&debug_key, "yes");
std::env::set_var(&list_key, "3000, 3001, 3002");
}
#[test]
/// Tests that `get_or` returns the default value when the environment variable is missing.
fn test_get_or() {
let env = EnvLoader;
unsafe {
std::env::remove_var("MISSING_VAR");
std::env::set_var("TEST_VAR", "test_value");
}
assert_eq!(env.get_or::<String>("MISSING_VAR", "default".to_string()), "default".to_string());
assert_eq!(env.get_or::<String>("TEST_VAR", "default".to_string()), "test_value".to_string());
assert_eq!(env.get::<String>(&name_key), Ok("envkit".to_string()));
assert_eq!(env.get::<u16>(&port_key), Ok(3000));
assert_eq!(env.get::<bool>(&debug_key), Ok(true));
assert_eq!(env.get::<Vec<u16>>(&list_key), Ok(vec![3000, 3001, 3002]));
unsafe {
std::env::remove_var(name_key);
std::env::remove_var(port_key);
std::env::remove_var(debug_key);
std::env::remove_var(list_key);
}
}
#[test]
fn get_reports_missing_and_invalid_values() {
let env = EnvLoader::new();
let missing_key = unique_key("MISSING");
let invalid_key = unique_key("INVALID");
unsafe {
std::env::remove_var(&missing_key);
std::env::set_var(&invalid_key, "not-a-port");
}
#[test]
/// Tests that `get_opt` returns `Some` when the environment variable exists.
fn test_get_opt() {
let env = EnvLoader;
unsafe {
std::env::set_var("OPTIONAL_VAR", "optional_value");
}
assert_eq!(
env.get::<String>(&missing_key),
Err(EnvError::MissingVar(missing_key.clone()))
);
assert_eq!(env.get_opt::<String>("OPTIONAL_VAR"), Some("optional_value".to_string()));
assert_eq!(env.get_opt::<bool>("DEV"), None);
assert!(matches!(
env.get::<u16>(&invalid_key),
Err(EnvError::Invalid { key, value, .. })
if key == "u16" && value == "not-a-port"
));
unsafe {
std::env::remove_var(invalid_key);
}
}
}
#[test]
fn get_or_and_get_opt_handle_fallbacks() {
let env = EnvLoader::new();
let present_key = unique_key("PRESENT");
let missing_key = unique_key("DEFAULT");
let invalid_key = unique_key("BAD_BOOL");
unsafe {
std::env::set_var(&present_key, "42");
std::env::remove_var(&missing_key);
std::env::set_var(&invalid_key, "maybe");
}
assert_eq!(env.get_or::<u8>(&present_key, 7), 42);
assert_eq!(env.get_or::<u8>(&missing_key, 7), 7);
assert_eq!(env.get_or::<bool>(&invalid_key, false), false);
assert_eq!(env.get_opt::<u8>(&present_key), Some(42));
assert_eq!(env.get_opt::<u8>(&missing_key), None);
assert_eq!(env.get_opt::<bool>(&invalid_key), None);
unsafe {
std::env::remove_var(present_key);
std::env::remove_var(invalid_key);
}
}
#[test]
fn prefixed_loader_reads_namespaced_variables() {
let env = EnvLoader::new();
let prefix = unique_key("APP_");
let host_key = format!("{prefix}HOST");
let enabled_key = format!("{prefix}ENABLED");
unsafe {
std::env::set_var(&host_key, "127.0.0.1");
std::env::set_var(&enabled_key, "1");
}
let app = env.with_prefix(prefix);
assert_eq!(app.get::<String>("HOST"), Ok("127.0.0.1".to_string()));
assert_eq!(app.get::<bool>("ENABLED"), Ok(true));
assert_eq!(app.get_or::<u16>("PORT", 8080), 8080);
unsafe {
std::env::remove_var(host_key);
std::env::remove_var(enabled_key);
}
}
#[test]
fn load_file_sets_environment_variables() {
let env = EnvLoader::new();
let prefix = unique_key("FILE_");
let port_key = format!("{prefix}PORT");
let debug_key = format!("{prefix}DEBUG");
let names_key = format!("{prefix}NAMES");
let path = temp_env_file(&prefix, &port_key, &debug_key, &names_key);
env.load_file(path.to_str().expect("test path should be UTF-8"))
.expect("env file should load");
assert_eq!(env.get::<u16>(&port_key), Ok(8080));
assert_eq!(env.get::<bool>(&debug_key), Ok(true));
assert_eq!(
env.get::<Vec<String>>(&names_key),
Ok(vec!["api".to_string(), "worker".to_string()])
);
std::fs::remove_file(path).expect("test env file should be removable");
unsafe {
std::env::remove_var(port_key);
std::env::remove_var(debug_key);
std::env::remove_var(names_key);
}
}
fn temp_env_file(prefix: &str, port_key: &str, debug_key: &str, names_key: &str) -> PathBuf {
let path = std::env::temp_dir().join(format!("{prefix}.env"));
let contents = format!(
r#"
# comments and blank lines are ignored
{port_key} = 8080
{debug_key}= yes
{names_key}= api, worker
"#
);
std::fs::write(&path, contents).expect("test env file should be writable");
path
}