use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentConfig {
#[serde(default)]
pub global_env: HashMap<String, EnvValue>,
#[serde(default)]
pub secrets: HashMap<String, SecretValue>,
#[serde(default)]
pub env_files: Vec<PathBuf>,
#[serde(default = "default_true")]
pub inherit: bool,
#[serde(default)]
pub profiles: HashMap<String, EnvProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active_profile: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EnvValue {
Static(String),
Dynamic(DynamicEnv),
Conditional(ConditionalEnv),
}
impl EnvValue {
pub fn static_value(value: impl Into<String>) -> Self {
EnvValue::Static(value.into())
}
pub fn is_static(&self) -> bool {
matches!(self, EnvValue::Static(_))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicEnv {
pub command: String,
#[serde(default)]
pub cache: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionalEnv {
pub condition: String,
pub when_true: String,
pub when_false: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SecretValue {
Simple(String),
Provider {
provider: SecretProvider,
key: String,
#[serde(skip_serializing_if = "Option::is_none")]
version: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SecretProvider {
Env,
File,
Vault,
Aws,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvProfile {
#[serde(flatten)]
pub env: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StepEnvironment {
#[serde(default)]
pub env: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_dir: Option<PathBuf>,
#[serde(default)]
pub clear_env: bool,
#[serde(default)]
pub temporary: bool,
}
impl Default for EnvironmentConfig {
fn default() -> Self {
Self {
global_env: HashMap::new(),
secrets: HashMap::new(),
env_files: Vec::new(),
inherit: true, profiles: HashMap::new(),
active_profile: None,
}
}
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_env_value_static() {
let value = EnvValue::static_value("test");
assert!(value.is_static());
if let EnvValue::Static(s) = value {
assert_eq!(s, "test");
} else {
panic!("Expected static value");
}
}
#[test]
fn test_env_value_dynamic() {
let value = EnvValue::Dynamic(DynamicEnv {
command: "echo hello".to_string(),
cache: true,
});
assert!(!value.is_static());
}
#[test]
fn test_env_value_conditional() {
let value = EnvValue::Conditional(ConditionalEnv {
condition: "branch == 'main'".to_string(),
when_true: "production".to_string(),
when_false: "staging".to_string(),
});
assert!(!value.is_static());
}
#[test]
fn test_environment_config_default() {
let config = EnvironmentConfig::default();
assert!(config.inherit);
assert!(config.global_env.is_empty());
assert!(config.secrets.is_empty());
assert!(config.env_files.is_empty());
assert!(config.profiles.is_empty());
assert!(config.active_profile.is_none());
}
#[test]
fn test_step_environment_default() {
let step_env = StepEnvironment::default();
assert!(!step_env.clear_env);
assert!(!step_env.temporary);
assert!(step_env.env.is_empty());
assert!(step_env.working_dir.is_none());
}
}