use crate::application::cli::error::CliError;
use std::collections::HashMap;
pub struct EnvTemplate;
impl EnvTemplate {
pub fn new() -> Self {
Self
}
pub fn generate<K>(
&self,
api_keys: &HashMap<K, String>,
existing_content: Option<&str>,
) -> Result<String, CliError>
where
K: std::fmt::Debug + std::hash::Hash + Eq,
{
let mut content = String::new();
content.push_str("# Paladin Environment Configuration\n");
content.push_str("# Generated by 'paladin onboarding'\n\n");
content.push_str("# LLM Provider Configuration\n");
for (key, value) in api_keys {
let key_name = format!("{:?}", key).to_uppercase();
let env_var = if key_name.contains("OPENAI") {
"OPENAI_API_KEY"
} else if key_name.contains("ANTHROPIC") {
"ANTHROPIC_API_KEY"
} else if key_name.contains("DEEPSEEK") {
"DEEPSEEK_API_KEY"
} else {
continue;
};
content.push_str(&format!("{}={}\n", env_var, value));
}
content.push('\n');
content.push_str("# Optional Services (uncomment to use)\n");
content.push_str("# REDIS_URL=redis://localhost:6379\n");
content.push_str("# QDRANT_URL=http://localhost:6333\n");
content.push_str("# MINIO_ENDPOINT=localhost:9000\n");
content.push_str("# MINIO_ACCESS_KEY=minioadmin\n");
content.push_str("# MINIO_SECRET_KEY=minioadmin\n");
if let Some(existing) = existing_content {
content = self.merge_env_content(&content, existing)?;
}
Ok(content)
}
fn merge_env_content(&self, new_content: &str, existing: &str) -> Result<String, CliError> {
let mut merged = HashMap::new();
let mut comments = Vec::new();
for line in existing.lines() {
if line.trim().starts_with('#') || line.trim().is_empty() {
comments.push(line.to_string());
} else if let Some((key, value)) = line.split_once('=') {
merged.insert(key.trim().to_string(), value.trim().to_string());
}
}
for line in new_content.lines() {
if line.trim().starts_with('#') || line.trim().is_empty() {
continue;
} else if let Some((key, value)) = line.split_once('=') {
merged.insert(key.trim().to_string(), value.trim().to_string());
}
}
let mut result = String::new();
result.push_str("# Paladin Environment Configuration\n");
result.push_str("# Merged by 'paladin onboarding'\n\n");
result.push_str("# LLM Provider Configuration\n");
for key in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "DEEPSEEK_API_KEY"] {
if let Some(value) = merged.get(key) {
result.push_str(&format!("{}={}\n", key, value));
}
}
result.push('\n');
result.push_str("# Other Configuration\n");
for (key, value) in &merged {
if !key.ends_with("_API_KEY") {
result.push_str(&format!("{}={}\n", key, value));
}
}
Ok(result)
}
}
impl Default for EnvTemplate {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_env_basic() {
let template = EnvTemplate::new();
let mut keys = HashMap::new();
keys.insert("OpenAI", "sk-test123".to_string());
let result = template.generate(&keys, None).unwrap();
assert!(result.contains("OPENAI_API_KEY=sk-test123"));
assert!(result.contains("# LLM Provider Configuration"));
assert!(result.contains("# Optional Services"));
}
#[test]
fn test_merge_env_content() {
let template = EnvTemplate::new();
let existing = "OPENAI_API_KEY=old-key\nCUSTOM_VAR=value1\n";
let new = "# New config\nOPENAI_API_KEY=new-key\nANTHROPIC_API_KEY=claude-key\n";
let result = template.merge_env_content(new, existing).unwrap();
assert!(result.contains("OPENAI_API_KEY=new-key")); assert!(result.contains("ANTHROPIC_API_KEY=claude-key")); assert!(result.contains("CUSTOM_VAR=value1")); assert!(!result.contains("old-key")); }
}