use crate::workspace::core::{EntityId, Environment, EnvironmentColor};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct EnvironmentManager {
environments: HashMap<EntityId, Environment>,
active_environment_id: Option<EntityId>,
}
#[derive(Debug, Clone)]
pub struct VariableSubstitution {
pub value: String,
pub success: bool,
pub errors: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct EnvironmentValidationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum EnvironmentExportFormat {
Json,
Yaml,
DotEnv,
Custom(String),
}
impl EnvironmentManager {
pub fn new() -> Self {
Self {
environments: HashMap::new(),
active_environment_id: None,
}
}
pub fn add_environment(&mut self, environment: Environment) -> EntityId {
let id = environment.id.clone();
self.environments.insert(id.clone(), environment);
if self.environments.len() == 1 {
self.active_environment_id = Some(id.clone());
if let Some(env) = self.environments.get_mut(&id) {
env.active = true;
}
}
id
}
pub fn get_environment(&self, id: &EntityId) -> Option<&Environment> {
self.environments.get(id)
}
pub fn get_environment_mut(&mut self, id: &EntityId) -> Option<&mut Environment> {
self.environments.get_mut(id)
}
pub fn remove_environment(&mut self, id: &EntityId) -> Result<Environment, String> {
if let Some(environment) = self.environments.remove(id) {
if self.active_environment_id.as_ref() == Some(id) {
self.active_environment_id = self.environments.keys().next().cloned();
if let Some(active_id) = &self.active_environment_id {
if let Some(env) = self.environments.get_mut(active_id) {
env.active = true;
}
}
}
Ok(environment)
} else {
Err(format!("Environment with ID {} not found", id))
}
}
pub fn get_all_environments(&self) -> Vec<&Environment> {
self.environments.values().collect()
}
pub fn get_active_environment(&self) -> Option<&Environment> {
self.active_environment_id.as_ref().and_then(|id| self.environments.get(id))
}
pub fn set_active_environment(&mut self, id: EntityId) -> Result<(), String> {
if self.environments.contains_key(&id) {
for environment in self.environments.values_mut() {
environment.active = false;
}
if let Some(env) = self.environments.get_mut(&id) {
env.active = true;
self.active_environment_id = Some(id);
}
Ok(())
} else {
Err(format!("Environment with ID {} not found", id))
}
}
pub fn substitute_variables(&self, template: &str) -> VariableSubstitution {
let mut result = String::new();
let mut success = true;
let mut errors = Vec::new();
let variables = if let Some(active_env) = self.get_active_environment() {
&active_env.variables
} else {
&HashMap::new()
};
let mut chars = template.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '{' && chars.peek() == Some(&'{') {
chars.next(); if let Some(var_name) = self.parse_variable_name(&mut chars) {
if let Some(value) = variables.get(&var_name) {
result.push_str(value);
} else {
success = false;
errors.push(format!("Variable '{}' not found", var_name));
result.push_str(&format!("{{{{{}}}}}", var_name));
}
} else {
result.push_str("{{");
}
} else {
result.push(ch);
}
}
VariableSubstitution {
value: result,
success,
errors,
}
}
fn parse_variable_name(
&self,
chars: &mut std::iter::Peekable<std::str::Chars>,
) -> Option<String> {
let mut var_name = String::new();
while let Some(ch) = chars.peek() {
if *ch == '}' {
if let Some(next_ch) = chars.clone().nth(1) {
if next_ch == '}' {
chars.next(); chars.next(); break;
}
}
} else if ch.is_alphanumeric() || *ch == '_' || *ch == '-' || *ch == '.' {
var_name.push(*ch);
chars.next();
} else {
return None; }
}
if var_name.is_empty() {
None
} else {
Some(var_name)
}
}
pub fn validate_environment(&self, environment: &Environment) -> EnvironmentValidationResult {
let mut errors = Vec::new();
let mut warnings = Vec::new();
if environment.name.trim().is_empty() {
errors.push("Environment name cannot be empty".to_string());
}
let mut seen_variables = std::collections::HashSet::new();
for (key, value) in &environment.variables {
if !seen_variables.insert(key.clone()) {
errors.push(format!("Duplicate variable name: {}", key));
}
if key.trim().is_empty() {
errors.push("Variable key cannot be empty".to_string());
}
if value.trim().is_empty() {
warnings.push(format!("Variable '{}' has empty value", key));
}
}
EnvironmentValidationResult {
is_valid: errors.is_empty(),
errors,
warnings,
}
}
pub fn export_environment(
&self,
environment_id: &EntityId,
format: EnvironmentExportFormat,
) -> Result<String, String> {
let environment = self
.environments
.get(environment_id)
.ok_or_else(|| format!("Environment with ID {} not found", environment_id))?;
match format {
EnvironmentExportFormat::Json => serde_json::to_string_pretty(environment)
.map_err(|e| format!("Failed to serialize environment: {}", e)),
EnvironmentExportFormat::Yaml => serde_yaml::to_string(environment)
.map_err(|e| format!("Failed to serialize environment: {}", e)),
EnvironmentExportFormat::DotEnv => {
let mut result = String::new();
for (key, value) in &environment.variables {
result.push_str(&format!("{}={}\n", key, value));
}
Ok(result)
}
EnvironmentExportFormat::Custom(template) => {
let mut result = template.clone();
for (key, value) in &environment.variables {
let placeholder = format!("{{{{{}}}}}", key);
result = result.replace(&placeholder, value);
}
Ok(result)
}
}
}
pub fn import_environment(&mut self, json_data: &str) -> Result<EntityId, String> {
let environment: Environment = serde_json::from_str(json_data)
.map_err(|e| format!("Failed to deserialize environment: {}", e))?;
let validation = self.validate_environment(&environment);
if !validation.is_valid {
return Err(format!("Environment validation failed: {:?}", validation.errors));
}
Ok(self.add_environment(environment))
}
pub fn get_stats(&self) -> EnvironmentStats {
let total_variables =
self.environments.values().map(|env| env.variables.len()).sum::<usize>();
let active_count = self.environments.values().filter(|env| env.active).count();
EnvironmentStats {
total_environments: self.environments.len(),
total_variables,
active_environments: active_count,
}
}
pub fn find_environments_by_name(&self, name_query: &str) -> Vec<&Environment> {
let query_lower = name_query.to_lowercase();
self.environments
.values()
.filter(|env| env.name.to_lowercase().contains(&query_lower))
.collect()
}
pub fn get_all_variables(&self) -> HashMap<String, String> {
let mut all_vars = HashMap::new();
for environment in self.environments.values() {
for (key, value) in &environment.variables {
all_vars.insert(key.clone(), value.clone());
}
}
all_vars
}
pub fn clone_environment(
&mut self,
source_id: &EntityId,
new_name: String,
) -> Result<EntityId, String> {
let source_env = self
.environments
.get(source_id)
.ok_or_else(|| format!("Environment with ID {} not found", source_id))?;
let mut new_env = source_env.clone();
new_env.id = uuid::Uuid::new_v4().to_string();
new_env.name = new_name;
new_env.active = false;
new_env.created_at = Utc::now();
new_env.updated_at = Utc::now();
Ok(self.add_environment(new_env))
}
pub fn merge_environments(
&mut self,
environment_ids: &[EntityId],
merged_name: String,
) -> Result<EntityId, String> {
let mut merged_variables = HashMap::new();
for env_id in environment_ids {
let env = self
.environments
.get(env_id)
.ok_or_else(|| format!("Environment with ID {} not found", env_id))?;
for (key, value) in &env.variables {
merged_variables.insert(key.clone(), value.clone());
}
}
let mut merged_env = Environment::new(merged_name);
merged_env.variables = merged_variables;
Ok(self.add_environment(merged_env))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentStats {
pub total_environments: usize,
pub total_variables: usize,
pub active_environments: usize,
}
impl Default for EnvironmentManager {
fn default() -> Self {
Self::new()
}
}
pub struct EnvironmentValidator;
impl EnvironmentValidator {
pub fn validate_variable_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err("Variable name cannot be empty".to_string());
}
if !name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
return Err(
"Variable name can only contain letters, numbers, underscores, and hyphens"
.to_string(),
);
}
if name.starts_with('-') || name.ends_with('-') {
return Err("Variable name cannot start or end with hyphens".to_string());
}
Ok(())
}
pub fn validate_variable_value(value: &str) -> Result<(), String> {
if value.contains('\0') {
return Err("Variable value cannot contain null characters".to_string());
}
Ok(())
}
pub fn validate_color(_color: &EnvironmentColor) -> Result<(), String> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_variable_substitution() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("test".to_string());
env.set_variable("API_URL".to_string(), "https://api.example.com".to_string());
env.set_variable("VERSION".to_string(), "1.0.0".to_string());
manager.add_environment(env);
let result = manager.substitute_variables("API: {{API_URL}}, Version: {{VERSION}}");
assert!(result.success);
assert_eq!(result.value, "API: https://api.example.com, Version: 1.0.0");
}
#[test]
fn test_missing_variable_substitution() {
let manager = EnvironmentManager::new();
let result = manager.substitute_variables("Missing: {{MISSING_VAR}}");
assert!(!result.success);
assert!(result.errors.contains(&"Variable 'MISSING_VAR' not found".to_string()));
}
#[test]
fn test_environment_manager_new() {
let manager = EnvironmentManager::new();
assert!(manager.environments.is_empty());
assert!(manager.active_environment_id.is_none());
}
#[test]
fn test_environment_manager_default() {
let manager = EnvironmentManager::default();
assert!(manager.environments.is_empty());
}
#[test]
fn test_add_environment_first_becomes_active() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Dev".to_string());
env.set_variable("API_URL".to_string(), "http://localhost".to_string());
let id = manager.add_environment(env);
assert_eq!(manager.active_environment_id, Some(id.clone()));
assert!(manager.get_environment(&id).unwrap().active);
}
#[test]
fn test_add_environment_multiple() {
let mut manager = EnvironmentManager::new();
let env1 = Environment::new("Dev".to_string());
let env2 = Environment::new("Prod".to_string());
let id1 = manager.add_environment(env1);
let id2 = manager.add_environment(env2);
assert_eq!(manager.active_environment_id, Some(id1.clone()));
assert!(manager.get_environment(&id1).unwrap().active);
assert!(!manager.get_environment(&id2).unwrap().active);
}
#[test]
fn test_get_environment() {
let mut manager = EnvironmentManager::new();
let env = Environment::new("Test".to_string());
let id = manager.add_environment(env);
assert!(manager.get_environment(&id).is_some());
assert_eq!(manager.get_environment(&id).unwrap().name, "Test");
assert!(manager.get_environment(&"nonexistent".to_string()).is_none());
}
#[test]
fn test_get_environment_mut() {
let mut manager = EnvironmentManager::new();
let env = Environment::new("Test".to_string());
let id = manager.add_environment(env);
if let Some(env_mut) = manager.get_environment_mut(&id) {
env_mut.set_variable("KEY".to_string(), "VALUE".to_string());
}
assert_eq!(
manager.get_environment(&id).unwrap().get_variable("KEY"),
Some(&"VALUE".to_string())
);
}
#[test]
fn test_remove_environment_not_active() {
let mut manager = EnvironmentManager::new();
let env1 = Environment::new("Dev".to_string());
let env2 = Environment::new("Prod".to_string());
let id1 = manager.add_environment(env1);
let id2 = manager.add_environment(env2);
let removed = manager.remove_environment(&id2).unwrap();
assert_eq!(removed.name, "Prod");
assert!(manager.get_environment(&id2).is_none());
assert_eq!(manager.active_environment_id, Some(id1)); }
#[test]
fn test_remove_environment_active() {
let mut manager = EnvironmentManager::new();
let env1 = Environment::new("Dev".to_string());
let env2 = Environment::new("Prod".to_string());
let id1 = manager.add_environment(env1);
let id2 = manager.add_environment(env2);
let removed = manager.remove_environment(&id1).unwrap();
assert_eq!(removed.name, "Dev");
assert_eq!(manager.active_environment_id, Some(id2.clone())); assert!(manager.get_environment(&id2).unwrap().active);
}
#[test]
fn test_remove_environment_not_found() {
let mut manager = EnvironmentManager::new();
let result = manager.remove_environment(&"nonexistent".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}
#[test]
fn test_get_all_environments() {
let mut manager = EnvironmentManager::new();
manager.add_environment(Environment::new("Dev".to_string()));
manager.add_environment(Environment::new("Prod".to_string()));
let all = manager.get_all_environments();
assert_eq!(all.len(), 2);
}
#[test]
fn test_get_active_environment() {
let mut manager = EnvironmentManager::new();
let env = Environment::new("Dev".to_string());
let id = manager.add_environment(env);
let active = manager.get_active_environment();
assert!(active.is_some());
assert_eq!(active.unwrap().id, id);
}
#[test]
fn test_get_active_environment_none() {
let manager = EnvironmentManager::new();
assert!(manager.get_active_environment().is_none());
}
#[test]
fn test_set_active_environment() {
let mut manager = EnvironmentManager::new();
let env1 = Environment::new("Dev".to_string());
let env2 = Environment::new("Prod".to_string());
let id1 = manager.add_environment(env1);
let id2 = manager.add_environment(env2);
manager.set_active_environment(id2.clone()).unwrap();
assert_eq!(manager.active_environment_id, Some(id2.clone()));
assert!(!manager.get_environment(&id1).unwrap().active);
assert!(manager.get_environment(&id2).unwrap().active);
}
#[test]
fn test_set_active_environment_not_found() {
let mut manager = EnvironmentManager::new();
let result = manager.set_active_environment("nonexistent".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}
#[test]
fn test_substitute_variables_no_active() {
let manager = EnvironmentManager::new();
let result = manager.substitute_variables("{{VAR}}");
assert!(!result.success);
assert!(result.errors.contains(&"Variable 'VAR' not found".to_string()));
}
#[test]
fn test_substitute_variables_invalid_syntax() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), "value".to_string());
manager.add_environment(env);
let result = manager.substitute_variables("Text {{VAR");
assert_eq!(result.value, "Text value");
}
#[test]
fn test_substitute_variables_invalid_characters() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), "value".to_string());
manager.add_environment(env);
let result = manager.substitute_variables("{{VAR@INVALID}}");
assert!(result.value.contains("{{"));
}
#[test]
fn test_substitute_variables_empty_name() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), "value".to_string());
manager.add_environment(env);
let result = manager.substitute_variables("{{}}");
assert!(result.value.contains("{{"));
}
#[test]
fn test_validate_environment_empty_name() {
let manager = EnvironmentManager::new();
let mut env = Environment::new(" ".to_string()); env.set_variable("VAR".to_string(), "value".to_string());
let result = manager.validate_environment(&env);
assert!(!result.is_valid);
assert!(result.errors.contains(&"Environment name cannot be empty".to_string()));
}
#[test]
fn test_validate_environment_duplicate_variables() {
let manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), "value1".to_string());
env.set_variable("VAR".to_string(), "value2".to_string());
let result = manager.validate_environment(&env);
assert!(result.is_valid || !result.errors.is_empty());
}
#[test]
fn test_validate_environment_empty_key() {
let manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable(" ".to_string(), "value".to_string());
let result = manager.validate_environment(&env);
assert!(!result.is_valid);
assert!(result.errors.iter().any(|e| e.contains("empty")));
}
#[test]
fn test_validate_environment_empty_value_warning() {
let manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), " ".to_string());
let result = manager.validate_environment(&env);
assert!(result.is_valid); assert!(result.warnings.iter().any(|w| w.contains("empty value")));
}
#[test]
fn test_validate_environment_valid() {
let manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR1".to_string(), "value1".to_string());
env.set_variable("VAR2".to_string(), "value2".to_string());
let result = manager.validate_environment(&env);
assert!(result.is_valid);
assert!(result.errors.is_empty());
}
#[test]
fn test_export_environment_json() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), "value".to_string());
let id = manager.add_environment(env);
let result = manager.export_environment(&id, EnvironmentExportFormat::Json);
assert!(result.is_ok());
assert!(result.unwrap().contains("Test"));
}
#[test]
fn test_export_environment_yaml() {
let mut manager = EnvironmentManager::new();
let env = Environment::new("Test".to_string());
let id = manager.add_environment(env);
let result = manager.export_environment(&id, EnvironmentExportFormat::Yaml);
assert!(result.is_ok());
}
#[test]
fn test_export_environment_dotenv() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR1".to_string(), "value1".to_string());
env.set_variable("VAR2".to_string(), "value2".to_string());
let id = manager.add_environment(env);
let result = manager.export_environment(&id, EnvironmentExportFormat::DotEnv);
assert!(result.is_ok());
let content = result.unwrap();
assert!(content.contains("VAR1=value1"));
assert!(content.contains("VAR2=value2"));
}
#[test]
fn test_export_environment_custom() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Test".to_string());
env.set_variable("VAR".to_string(), "value".to_string());
let id = manager.add_environment(env);
let template = "Config: {{VAR}}";
let result =
manager.export_environment(&id, EnvironmentExportFormat::Custom(template.to_string()));
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Config: value");
}
#[test]
fn test_export_environment_not_found() {
let manager = EnvironmentManager::new();
let result =
manager.export_environment(&"nonexistent".to_string(), EnvironmentExportFormat::Json);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}
#[test]
fn test_import_environment() {
let mut manager = EnvironmentManager::new();
let env = Environment::new("Test".to_string());
let json = serde_json::to_string(&env).unwrap();
let result = manager.import_environment(&json);
assert!(result.is_ok());
assert_eq!(manager.get_all_environments().len(), 1);
}
#[test]
fn test_import_environment_invalid_json() {
let mut manager = EnvironmentManager::new();
let result = manager.import_environment("invalid json");
assert!(result.is_err());
}
#[test]
fn test_get_stats() {
let mut manager = EnvironmentManager::new();
let mut env1 = Environment::new("Dev".to_string());
env1.set_variable("VAR1".to_string(), "value1".to_string());
let mut env2 = Environment::new("Prod".to_string());
env2.set_variable("VAR2".to_string(), "value2".to_string());
manager.add_environment(env1);
manager.add_environment(env2);
let stats = manager.get_stats();
assert_eq!(stats.total_environments, 2);
assert_eq!(stats.total_variables, 2);
assert_eq!(stats.active_environments, 1); }
#[test]
fn test_find_environments_by_name() {
let mut manager = EnvironmentManager::new();
manager.add_environment(Environment::new("Development".to_string()));
manager.add_environment(Environment::new("Production".to_string()));
manager.add_environment(Environment::new("Staging".to_string()));
let results = manager.find_environments_by_name("dev");
assert_eq!(results.len(), 1);
assert_eq!(results[0].name, "Development");
}
#[test]
fn test_get_all_variables() {
let mut manager = EnvironmentManager::new();
let mut env1 = Environment::new("Dev".to_string());
env1.set_variable("VAR1".to_string(), "value1".to_string());
let mut env2 = Environment::new("Prod".to_string());
env2.set_variable("VAR2".to_string(), "value2".to_string());
manager.add_environment(env1);
manager.add_environment(env2);
let all_vars = manager.get_all_variables();
assert_eq!(all_vars.len(), 2);
assert_eq!(all_vars.get("VAR1"), Some(&"value1".to_string()));
assert_eq!(all_vars.get("VAR2"), Some(&"value2".to_string()));
}
#[test]
fn test_clone_environment() {
let mut manager = EnvironmentManager::new();
let mut env = Environment::new("Source".to_string());
env.set_variable("VAR".to_string(), "value".to_string());
let source_id = manager.add_environment(env);
let cloned_id = manager.clone_environment(&source_id, "Cloned".to_string()).unwrap();
assert_ne!(cloned_id, source_id);
let cloned = manager.get_environment(&cloned_id).unwrap();
assert_eq!(cloned.name, "Cloned");
assert_eq!(cloned.get_variable("VAR"), Some(&"value".to_string()));
assert!(!cloned.active); }
#[test]
fn test_clone_environment_not_found() {
let mut manager = EnvironmentManager::new();
let result = manager.clone_environment(&"nonexistent".to_string(), "New".to_string());
assert!(result.is_err());
}
#[test]
fn test_merge_environments() {
let mut manager = EnvironmentManager::new();
let mut env1 = Environment::new("Dev".to_string());
env1.set_variable("VAR1".to_string(), "value1".to_string());
let mut env2 = Environment::new("Prod".to_string());
env2.set_variable("VAR2".to_string(), "value2".to_string());
let id1 = manager.add_environment(env1);
let id2 = manager.add_environment(env2);
let merged_id = manager.merge_environments(&[id1, id2], "Merged".to_string()).unwrap();
let merged = manager.get_environment(&merged_id).unwrap();
assert_eq!(merged.name, "Merged");
assert_eq!(merged.get_variable("VAR1"), Some(&"value1".to_string()));
assert_eq!(merged.get_variable("VAR2"), Some(&"value2".to_string()));
}
#[test]
fn test_merge_environments_not_found() {
let mut manager = EnvironmentManager::new();
let result = manager.merge_environments(&["nonexistent".to_string()], "Merged".to_string());
assert!(result.is_err());
}
#[test]
fn test_environment_validator_validate_variable_name() {
assert!(EnvironmentValidator::validate_variable_name("VALID_NAME").is_ok());
assert!(EnvironmentValidator::validate_variable_name("VALID_NAME_123").is_ok());
assert!(EnvironmentValidator::validate_variable_name("valid-name").is_ok());
assert!(EnvironmentValidator::validate_variable_name("").is_err());
assert!(EnvironmentValidator::validate_variable_name("INVALID@NAME").is_err());
assert!(EnvironmentValidator::validate_variable_name("-INVALID").is_err());
assert!(EnvironmentValidator::validate_variable_name("INVALID-").is_err());
}
#[test]
fn test_environment_validator_validate_variable_value() {
assert!(EnvironmentValidator::validate_variable_value("valid value").is_ok());
assert!(EnvironmentValidator::validate_variable_value("").is_ok());
let mut invalid_value = String::from("valid");
invalid_value.push('\0');
assert!(EnvironmentValidator::validate_variable_value(&invalid_value).is_err());
}
#[test]
fn test_environment_validator_validate_color() {
let color = EnvironmentColor::new(255, 128, 64);
assert!(EnvironmentValidator::validate_color(&color).is_ok());
}
}