mockforge_core/workspace/
environment.rs1use crate::workspace::core::{EntityId, Environment, EnvironmentColor};
7use chrono::Utc;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
13pub struct EnvironmentManager {
14 environments: HashMap<EntityId, Environment>,
16 active_environment_id: Option<EntityId>,
18}
19
20#[derive(Debug, Clone)]
22pub struct VariableSubstitution {
23 pub value: String,
25 pub success: bool,
27 pub errors: Vec<String>,
29}
30
31#[derive(Debug, Clone)]
33pub struct EnvironmentValidationResult {
34 pub is_valid: bool,
36 pub errors: Vec<String>,
38 pub warnings: Vec<String>,
40}
41
42#[derive(Debug, Clone)]
44pub enum EnvironmentExportFormat {
45 Json,
47 Yaml,
49 DotEnv,
51 Custom(String),
53}
54
55impl EnvironmentManager {
56 pub fn new() -> Self {
58 Self {
59 environments: HashMap::new(),
60 active_environment_id: None,
61 }
62 }
63
64 pub fn add_environment(&mut self, environment: Environment) -> EntityId {
66 let id = environment.id.clone();
67 self.environments.insert(id.clone(), environment);
68
69 if self.environments.len() == 1 {
71 self.active_environment_id = Some(id.clone());
72 if let Some(env) = self.environments.get_mut(&id) {
73 env.active = true;
74 }
75 }
76
77 id
78 }
79
80 pub fn get_environment(&self, id: &EntityId) -> Option<&Environment> {
82 self.environments.get(id)
83 }
84
85 pub fn get_environment_mut(&mut self, id: &EntityId) -> Option<&mut Environment> {
87 self.environments.get_mut(id)
88 }
89
90 pub fn remove_environment(&mut self, id: &EntityId) -> Result<Environment, String> {
92 if let Some(environment) = self.environments.remove(id) {
93 if self.active_environment_id.as_ref() == Some(id) {
95 self.active_environment_id = self.environments.keys().next().cloned();
96 if let Some(active_id) = &self.active_environment_id {
97 if let Some(env) = self.environments.get_mut(active_id) {
98 env.active = true;
99 }
100 }
101 }
102
103 Ok(environment)
104 } else {
105 Err(format!("Environment with ID {} not found", id))
106 }
107 }
108
109 pub fn get_all_environments(&self) -> Vec<&Environment> {
111 self.environments.values().collect()
112 }
113
114 pub fn get_active_environment(&self) -> Option<&Environment> {
116 self.active_environment_id.as_ref().and_then(|id| self.environments.get(id))
117 }
118
119 pub fn set_active_environment(&mut self, id: EntityId) -> Result<(), String> {
121 if self.environments.contains_key(&id) {
122 for environment in self.environments.values_mut() {
124 environment.active = false;
125 }
126
127 if let Some(env) = self.environments.get_mut(&id) {
129 env.active = true;
130 self.active_environment_id = Some(id);
131 }
132
133 Ok(())
134 } else {
135 Err(format!("Environment with ID {} not found", id))
136 }
137 }
138
139 pub fn substitute_variables(&self, template: &str) -> VariableSubstitution {
141 let mut result = String::new();
142 let mut success = true;
143 let mut errors = Vec::new();
144
145 let variables = if let Some(active_env) = self.get_active_environment() {
147 &active_env.variables
148 } else {
149 &std::collections::HashMap::new()
151 };
152
153 let mut chars = template.chars().peekable();
154 while let Some(ch) = chars.next() {
155 if ch == '{' && chars.peek() == Some(&'{') {
156 chars.next(); if let Some(var_name) = self.parse_variable_name(&mut chars) {
159 if let Some(value) = variables.get(&var_name) {
160 result.push_str(value);
161 } else {
162 success = false;
163 errors.push(format!("Variable '{}' not found", var_name));
164 result.push_str(&format!("{{{{{}}}}}", var_name));
165 }
166 } else {
167 result.push_str("{{");
168 }
169 } else {
170 result.push(ch);
171 }
172 }
173
174 VariableSubstitution {
175 value: result,
176 success,
177 errors,
178 }
179 }
180
181 fn parse_variable_name(
183 &self,
184 chars: &mut std::iter::Peekable<std::str::Chars>,
185 ) -> Option<String> {
186 let mut var_name = String::new();
187
188 while let Some(ch) = chars.peek() {
189 if *ch == '}' {
190 if let Some(next_ch) = chars.clone().nth(1) {
191 if next_ch == '}' {
192 chars.next(); chars.next(); break;
196 }
197 }
198 } else if ch.is_alphanumeric() || *ch == '_' || *ch == '-' || *ch == '.' {
199 var_name.push(*ch);
200 chars.next();
201 } else {
202 return None; }
204 }
205
206 if var_name.is_empty() {
207 None
208 } else {
209 Some(var_name)
210 }
211 }
212
213 pub fn validate_environment(&self, environment: &Environment) -> EnvironmentValidationResult {
215 let mut errors = Vec::new();
216 let mut warnings = Vec::new();
217
218 if environment.name.trim().is_empty() {
220 errors.push("Environment name cannot be empty".to_string());
221 }
222
223 let mut seen_variables = std::collections::HashSet::new();
225 for (key, value) in &environment.variables {
226 if !seen_variables.insert(key.clone()) {
227 errors.push(format!("Duplicate variable name: {}", key));
228 }
229
230 if key.trim().is_empty() {
232 errors.push("Variable key cannot be empty".to_string());
233 }
234
235 if value.trim().is_empty() {
237 warnings.push(format!("Variable '{}' has empty value", key));
238 }
239 }
240
241 EnvironmentValidationResult {
244 is_valid: errors.is_empty(),
245 errors,
246 warnings,
247 }
248 }
249
250 pub fn export_environment(
252 &self,
253 environment_id: &EntityId,
254 format: EnvironmentExportFormat,
255 ) -> Result<String, String> {
256 let environment = self
257 .environments
258 .get(environment_id)
259 .ok_or_else(|| format!("Environment with ID {} not found", environment_id))?;
260
261 match format {
262 EnvironmentExportFormat::Json => serde_json::to_string_pretty(environment)
263 .map_err(|e| format!("Failed to serialize environment: {}", e)),
264 EnvironmentExportFormat::Yaml => serde_yaml::to_string(environment)
265 .map_err(|e| format!("Failed to serialize environment: {}", e)),
266 EnvironmentExportFormat::DotEnv => {
267 let mut result = String::new();
268 for (key, value) in &environment.variables {
269 result.push_str(&format!("{}={}\n", key, value));
270 }
271 Ok(result)
272 }
273 EnvironmentExportFormat::Custom(template) => {
274 let mut result = template.clone();
275 for (key, value) in &environment.variables {
276 let placeholder = format!("{{{{{}}}}}", key);
277 result = result.replace(&placeholder, value);
278 }
279 Ok(result)
280 }
281 }
282 }
283
284 pub fn import_environment(&mut self, json_data: &str) -> Result<EntityId, String> {
286 let environment: Environment = serde_json::from_str(json_data)
287 .map_err(|e| format!("Failed to deserialize environment: {}", e))?;
288
289 let validation = self.validate_environment(&environment);
291 if !validation.is_valid {
292 return Err(format!("Environment validation failed: {:?}", validation.errors));
293 }
294
295 Ok(self.add_environment(environment))
296 }
297
298 pub fn get_stats(&self) -> EnvironmentStats {
300 let total_variables =
301 self.environments.values().map(|env| env.variables.len()).sum::<usize>();
302
303 let active_count = self.environments.values().filter(|env| env.active).count();
304
305 EnvironmentStats {
306 total_environments: self.environments.len(),
307 total_variables,
308 active_environments: active_count,
309 }
310 }
311
312 pub fn find_environments_by_name(&self, name_query: &str) -> Vec<&Environment> {
314 let query_lower = name_query.to_lowercase();
315 self.environments
316 .values()
317 .filter(|env| env.name.to_lowercase().contains(&query_lower))
318 .collect()
319 }
320
321 pub fn get_all_variables(&self) -> HashMap<String, String> {
323 let mut all_vars = HashMap::new();
324
325 for environment in self.environments.values() {
326 for (key, value) in &environment.variables {
327 all_vars.insert(key.clone(), value.clone());
328 }
329 }
330
331 all_vars
332 }
333
334 pub fn clone_environment(
336 &mut self,
337 source_id: &EntityId,
338 new_name: String,
339 ) -> Result<EntityId, String> {
340 let source_env = self
341 .environments
342 .get(source_id)
343 .ok_or_else(|| format!("Environment with ID {} not found", source_id))?;
344
345 let mut new_env = source_env.clone();
346 new_env.id = uuid::Uuid::new_v4().to_string();
347 new_env.name = new_name;
348 new_env.active = false;
349 new_env.created_at = Utc::now();
350 new_env.updated_at = Utc::now();
351
352 Ok(self.add_environment(new_env))
353 }
354
355 pub fn merge_environments(
357 &mut self,
358 environment_ids: &[EntityId],
359 merged_name: String,
360 ) -> Result<EntityId, String> {
361 let mut merged_variables = HashMap::new();
362
363 for env_id in environment_ids {
364 let env = self
365 .environments
366 .get(env_id)
367 .ok_or_else(|| format!("Environment with ID {} not found", env_id))?;
368
369 for (key, value) in &env.variables {
370 merged_variables.insert(key.clone(), value.clone());
371 }
372 }
373
374 let mut merged_env = Environment::new(merged_name);
375 merged_env.variables = merged_variables;
376
377 Ok(self.add_environment(merged_env))
378 }
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct EnvironmentStats {
384 pub total_environments: usize,
386 pub total_variables: usize,
388 pub active_environments: usize,
390}
391
392impl Default for EnvironmentManager {
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398pub struct EnvironmentValidator;
400
401impl EnvironmentValidator {
402 pub fn validate_variable_name(name: &str) -> Result<(), String> {
404 if name.is_empty() {
405 return Err("Variable name cannot be empty".to_string());
406 }
407
408 if !name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
409 return Err(
410 "Variable name can only contain letters, numbers, underscores, and hyphens"
411 .to_string(),
412 );
413 }
414
415 if name.starts_with('-') || name.ends_with('-') {
416 return Err("Variable name cannot start or end with hyphens".to_string());
417 }
418
419 Ok(())
420 }
421
422 pub fn validate_variable_value(value: &str) -> Result<(), String> {
424 if value.contains('\0') {
425 return Err("Variable value cannot contain null characters".to_string());
426 }
427
428 Ok(())
429 }
430
431 pub fn validate_color(_color: &EnvironmentColor) -> Result<(), String> {
433 Ok(())
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_variable_substitution() {
444 let mut manager = EnvironmentManager::new();
445 let mut env = Environment::new("test".to_string());
446 env.set_variable("API_URL".to_string(), "https://api.example.com".to_string());
447 env.set_variable("VERSION".to_string(), "1.0.0".to_string());
448 manager.add_environment(env);
449
450 let result = manager.substitute_variables("API: {{API_URL}}, Version: {{VERSION}}");
451 assert!(result.success);
452 assert_eq!(result.value, "API: https://api.example.com, Version: 1.0.0");
453 }
454
455 #[test]
456 fn test_missing_variable_substitution() {
457 let manager = EnvironmentManager::new();
458 let result = manager.substitute_variables("Missing: {{MISSING_VAR}}");
459
460 assert!(!result.success);
461 assert!(result.errors.contains(&"Variable 'MISSING_VAR' not found".to_string()));
462 }
463}