use super::config::{EnvValue, EnvironmentConfig};
use super::context::ImmutableEnvironmentContext;
use anyhow::Result;
use std::collections::HashMap;
use std::path::PathBuf;
pub struct EnvironmentContextBuilder {
base_working_dir: PathBuf,
env_vars: HashMap<String, String>,
secret_keys: Vec<String>,
profile: Option<String>,
}
impl EnvironmentContextBuilder {
pub fn new(base_working_dir: PathBuf) -> Self {
Self {
base_working_dir,
env_vars: std::env::vars().collect(), secret_keys: Vec::new(),
profile: None,
}
}
pub fn with_env(mut self, key: String, value: String) -> Self {
self.env_vars.insert(key, value);
self
}
pub fn with_env_vars(mut self, vars: HashMap<String, String>) -> Self {
self.env_vars.extend(vars);
self
}
pub fn with_secret(mut self, key: String) -> Self {
if !self.secret_keys.contains(&key) {
self.secret_keys.push(key);
}
self
}
pub fn with_secrets(mut self, keys: Vec<String>) -> Self {
for key in keys {
if !self.secret_keys.contains(&key) {
self.secret_keys.push(key);
}
}
self
}
pub fn with_profile(mut self, profile: String) -> Self {
self.profile = Some(profile);
self
}
pub fn with_config(mut self, config: &EnvironmentConfig) -> Result<Self> {
if let Some(profile_name) = &config.active_profile {
self = self.with_profile(profile_name.clone());
if let Some(profile) = config.profiles.get(profile_name) {
self = self.apply_profile_vars(&profile.env)?;
}
}
for (key, value) in &config.global_env {
if let EnvValue::Static(s) = value {
self = self.with_env(key.clone(), s.clone());
}
}
for key in config.secrets.keys() {
self = self.with_secret(key.clone());
}
Ok(self)
}
fn apply_profile_vars(mut self, vars: &HashMap<String, String>) -> Result<Self> {
for (key, value) in vars {
self = self.with_env(key.clone(), value.clone());
}
Ok(self)
}
pub fn with_positional_args(mut self, args: &[String]) -> Self {
use super::pure::inject_positional_args;
inject_positional_args(&mut self.env_vars, args);
self
}
pub fn build(self) -> ImmutableEnvironmentContext {
use std::sync::Arc;
ImmutableEnvironmentContext {
base_working_dir: Arc::new(self.base_working_dir),
env_vars: Arc::new(self.env_vars),
secret_keys: Arc::new(self.secret_keys),
profile: self.profile.map(Arc::from),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_basic() {
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_env("KEY".to_string(), "value".to_string())
.with_secret("SECRET".to_string())
.build();
assert_eq!(context.working_dir(), PathBuf::from("/test").as_path());
assert_eq!(context.env_vars().get("KEY"), Some(&"value".to_string()));
assert!(context.is_secret("SECRET"));
}
#[test]
fn test_builder_multiple_env_vars() {
let mut vars = HashMap::new();
vars.insert("A".to_string(), "1".to_string());
vars.insert("B".to_string(), "2".to_string());
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_env_vars(vars)
.build();
assert_eq!(context.env_vars().get("A"), Some(&"1".to_string()));
assert_eq!(context.env_vars().get("B"), Some(&"2".to_string()));
}
#[test]
fn test_builder_with_profile() {
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_profile("production".to_string())
.build();
assert_eq!(context.profile(), Some("production"));
}
#[test]
fn test_builder_with_secrets() {
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_secrets(vec!["SECRET1".to_string(), "SECRET2".to_string()])
.build();
assert!(context.is_secret("SECRET1"));
assert!(context.is_secret("SECRET2"));
assert!(!context.is_secret("NOT_SECRET"));
}
#[test]
fn test_builder_with_config_empty() {
let config = EnvironmentConfig::default();
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_config(&config)
.unwrap()
.build();
assert_eq!(context.working_dir(), PathBuf::from("/test").as_path());
}
#[test]
fn test_builder_with_config_global_env() {
let mut config = EnvironmentConfig::default();
config.global_env.insert(
"GLOBAL_VAR".to_string(),
EnvValue::Static("value".to_string()),
);
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_config(&config)
.unwrap()
.build();
assert_eq!(
context.env_vars().get("GLOBAL_VAR"),
Some(&"value".to_string())
);
}
#[test]
fn test_builder_inherits_system_env() {
let context = EnvironmentContextBuilder::new(PathBuf::from("/test")).build();
assert!(!context.env_vars().is_empty());
}
#[test]
fn test_builder_chain() {
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_env("VAR1".to_string(), "value1".to_string())
.with_env("VAR2".to_string(), "value2".to_string())
.with_secret("VAR1".to_string())
.with_profile("prod".to_string())
.build();
assert_eq!(context.env_vars().get("VAR1"), Some(&"value1".to_string()));
assert_eq!(context.env_vars().get("VAR2"), Some(&"value2".to_string()));
assert!(context.is_secret("VAR1"));
assert!(!context.is_secret("VAR2"));
assert_eq!(context.profile(), Some("prod"));
}
#[test]
fn test_builder_duplicate_secrets() {
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_secret("SECRET".to_string())
.with_secret("SECRET".to_string()) .build();
assert!(context.is_secret("SECRET"));
assert_eq!(
context
.secret_keys()
.iter()
.filter(|k| *k == "SECRET")
.count(),
1
);
}
#[test]
fn test_with_positional_args() {
let args = vec!["file.txt".to_string(), "output.json".to_string()];
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_positional_args(&args)
.build();
assert_eq!(
context.env_vars().get("ARG_1"),
Some(&"file.txt".to_string())
);
assert_eq!(
context.env_vars().get("ARG_2"),
Some(&"output.json".to_string())
);
}
#[test]
fn test_with_positional_args_empty() {
let args: Vec<String> = vec![];
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_positional_args(&args)
.build();
assert!(context.env_vars().get("ARG_1").is_none());
}
#[test]
fn test_with_positional_args_chaining() {
let args = vec!["test.txt".to_string()];
let context = EnvironmentContextBuilder::new(PathBuf::from("/test"))
.with_env("CUSTOM".to_string(), "value".to_string())
.with_positional_args(&args)
.build();
assert_eq!(context.env_vars().get("CUSTOM"), Some(&"value".to_string()));
assert_eq!(
context.env_vars().get("ARG_1"),
Some(&"test.txt".to_string())
);
}
}