1use crate::EnvVarManager;
2use crate::snapshot::Profile;
3use color_eyre::Result;
4use color_eyre::eyre::eyre;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path::PathBuf;
9
10#[derive(Debug, Serialize, Deserialize)]
11struct ProfileConfig {
12 pub active: Option<String>,
13 pub profiles: HashMap<String, Profile>,
14}
15
16pub struct ProfileManager {
17 config_path: PathBuf,
18 config: ProfileConfig,
19}
20
21impl ProfileManager {
22 pub fn new() -> Result<Self> {
31 let config_dir = if cfg!(windows) {
32 dirs::data_dir()
33 .ok_or_else(|| eyre!("Could not find data directory"))?
34 .join("envx")
35 } else {
36 dirs::config_dir()
37 .ok_or_else(|| eyre!("Could not find config directory"))?
38 .join("envx")
39 };
40
41 fs::create_dir_all(&config_dir)?;
42 let config_path = config_dir.join("profiles.json");
43
44 let config = if config_path.exists() {
45 let content = fs::read_to_string(&config_path)?;
46 serde_json::from_str(&content)?
47 } else {
48 ProfileConfig {
49 active: None,
50 profiles: HashMap::new(),
51 }
52 };
53
54 Ok(Self { config_path, config })
55 }
56
57 pub fn create(&mut self, name: String, description: Option<String>) -> Result<()> {
65 if self.config.profiles.contains_key(&name) {
66 return Err(eyre!("Profile '{}' already exists", name));
67 }
68
69 let profile = Profile::new(name.clone(), description);
70 self.config.profiles.insert(name, profile);
71 self.save()?;
72 Ok(())
73 }
74
75 pub fn delete(&mut self, name: &str) -> Result<()> {
85 if self.config.active.as_ref() == Some(&name.to_string()) {
86 self.config.active = None;
87 }
88
89 self.config
90 .profiles
91 .remove(name)
92 .ok_or_else(|| color_eyre::eyre::eyre!("Profile '{}' not found", name))?;
93
94 self.save()?;
95 Ok(())
96 }
97
98 #[must_use]
99 pub fn list(&self) -> Vec<&Profile> {
100 self.config.profiles.values().collect()
101 }
102
103 #[must_use]
104 pub fn get(&self, name: &str) -> Option<&Profile> {
105 self.config.profiles.get(name)
106 }
107
108 pub fn get_mut(&mut self, name: &str) -> Option<&mut Profile> {
109 self.config.profiles.get_mut(name)
110 }
111
112 #[must_use]
113 pub fn active(&self) -> Option<&Profile> {
114 self.config
115 .active
116 .as_ref()
117 .and_then(|name| self.config.profiles.get(name))
118 }
119
120 pub fn switch(&mut self, name: &str) -> Result<()> {
128 if !self.config.profiles.contains_key(name) {
129 return Err(eyre!("Profile '{}' not found", name));
130 }
131
132 self.config.active = Some(name.to_string());
133 self.save()?;
134 Ok(())
135 }
136
137 pub fn apply(&self, name: &str, manager: &mut EnvVarManager) -> Result<()> {
150 let profile = self
151 .get(name)
152 .ok_or_else(|| color_eyre::eyre::eyre!("Profile '{}' not found", name))?;
153
154 if let Some(parent) = &profile.parent {
156 self.apply(parent, manager)?;
157 }
158
159 for (var_name, var) in &profile.variables {
161 if var.enabled {
162 manager.set(var_name, &var.value, true)?;
165 }
166 }
167
168 Ok(())
169 }
170
171 pub fn export(&self, name: &str) -> Result<String> {
179 let profile = self.get(name).ok_or_else(|| eyre!("Profile '{}' not found", name))?;
180
181 Ok(serde_json::to_string_pretty(profile)?)
182 }
183
184 pub fn import(&mut self, name: String, json: &str, overwrite: bool) -> Result<()> {
193 if !overwrite && self.config.profiles.contains_key(&name) {
194 return Err(eyre!("Profile '{}' already exists", name));
195 }
196
197 let mut profile: Profile = serde_json::from_str(json)?;
198 profile.name.clone_from(&name);
199
200 self.config.profiles.insert(name, profile);
201 self.save()?;
202 Ok(())
203 }
204
205 pub fn save(&self) -> Result<()> {
213 let content = serde_json::to_string_pretty(&self.config)?;
214 fs::write(&self.config_path, content)?;
215 Ok(())
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use crate::ProfileVar;
222
223 use super::*;
224 use tempfile::TempDir;
225
226 fn create_test_profile_manager() -> (ProfileManager, TempDir) {
227 let temp_dir = TempDir::new().unwrap();
228 let config_path = temp_dir.path().join("profiles.json");
229
230 let config = ProfileConfig {
231 active: None,
232 profiles: HashMap::new(),
233 };
234
235 let manager = ProfileManager { config_path, config };
236
237 (manager, temp_dir)
238 }
239
240 fn create_test_profile(name: &str) -> Profile {
241 let mut profile = Profile::new(name.to_string(), Some(format!("{name} description")));
242 profile.add_var("TEST_VAR".to_string(), "test_value".to_string(), false);
243 profile
244 }
245
246 #[test]
247 fn test_profile_manager_new() {
248 let (manager, _temp) = create_test_profile_manager();
250
251 assert!(manager.config.profiles.is_empty());
252 assert!(manager.config.active.is_none());
253 }
254
255 #[test]
256 fn test_profile_manager_new_with_existing_config() {
257 let temp_dir = TempDir::new().unwrap();
258 let config_path = temp_dir.path().join("profiles.json");
259
260 let mut profiles = HashMap::new();
262 profiles.insert("test".to_string(), create_test_profile("test"));
263
264 let config = ProfileConfig {
265 active: Some("test".to_string()),
266 profiles,
267 };
268
269 let content = serde_json::to_string_pretty(&config).unwrap();
270 fs::write(&config_path, content).unwrap();
271
272 let manager = ProfileManager {
274 config_path: config_path.clone(),
275 config: if config_path.exists() {
276 let content = fs::read_to_string(&config_path).unwrap();
277 serde_json::from_str(&content).unwrap()
278 } else {
279 ProfileConfig {
280 active: None,
281 profiles: HashMap::new(),
282 }
283 },
284 };
285
286 assert_eq!(manager.config.profiles.len(), 1);
287 assert_eq!(manager.config.active, Some("test".to_string()));
288 }
289
290 #[test]
291 fn test_create_profile() {
292 let (mut manager, _temp) = create_test_profile_manager();
293
294 let result = manager.create("dev".to_string(), Some("Development profile".to_string()));
295 assert!(result.is_ok());
296
297 assert_eq!(manager.config.profiles.len(), 1);
298 assert!(manager.config.profiles.contains_key("dev"));
299
300 let profile = manager.get("dev").unwrap();
301 assert_eq!(profile.name, "dev");
302 assert_eq!(profile.description, Some("Development profile".to_string()));
303 }
304
305 #[test]
306 fn test_create_duplicate_profile() {
307 let (mut manager, _temp) = create_test_profile_manager();
308
309 manager.create("dev".to_string(), None).unwrap();
310 let result = manager.create("dev".to_string(), None);
311
312 assert!(result.is_err());
313 assert!(result.unwrap_err().to_string().contains("already exists"));
314 }
315
316 #[test]
317 fn test_delete_profile() {
318 let (mut manager, _temp) = create_test_profile_manager();
319
320 manager.create("dev".to_string(), None).unwrap();
321 assert_eq!(manager.config.profiles.len(), 1);
322
323 let result = manager.delete("dev");
324 assert!(result.is_ok());
325 assert_eq!(manager.config.profiles.len(), 0);
326 }
327
328 #[test]
329 fn test_delete_active_profile() {
330 let (mut manager, _temp) = create_test_profile_manager();
331
332 manager.create("dev".to_string(), None).unwrap();
333 manager.switch("dev").unwrap();
334 assert_eq!(manager.config.active, Some("dev".to_string()));
335
336 let result = manager.delete("dev");
337 assert!(result.is_ok());
338 assert!(manager.config.active.is_none());
339 }
340
341 #[test]
342 fn test_delete_nonexistent_profile() {
343 let (mut manager, _temp) = create_test_profile_manager();
344
345 let result = manager.delete("nonexistent");
346 assert!(result.is_err());
347 assert!(result.unwrap_err().to_string().contains("not found"));
348 }
349
350 #[test]
351 fn test_list_profiles() {
352 let (mut manager, _temp) = create_test_profile_manager();
353
354 manager.create("dev".to_string(), None).unwrap();
355 manager.create("prod".to_string(), None).unwrap();
356 manager.create("test".to_string(), None).unwrap();
357
358 let profiles = manager.list();
359 assert_eq!(profiles.len(), 3);
360
361 let names: Vec<&str> = profiles.iter().map(|p| p.name.as_str()).collect();
362 assert!(names.contains(&"dev"));
363 assert!(names.contains(&"prod"));
364 assert!(names.contains(&"test"));
365 }
366
367 #[test]
368 fn test_get_profile() {
369 let (mut manager, _temp) = create_test_profile_manager();
370
371 manager.create("dev".to_string(), Some("Dev env".to_string())).unwrap();
372
373 let profile = manager.get("dev");
374 assert!(profile.is_some());
375 assert_eq!(profile.unwrap().description, Some("Dev env".to_string()));
376
377 let profile = manager.get("nonexistent");
378 assert!(profile.is_none());
379 }
380
381 #[test]
382 fn test_get_mut_profile() {
383 let (mut manager, _temp) = create_test_profile_manager();
384
385 manager.create("dev".to_string(), None).unwrap();
386
387 let profile = manager.get_mut("dev").unwrap();
388 profile.add_var("NEW_VAR".to_string(), "new_value".to_string(), false);
389
390 let profile = manager.get("dev").unwrap();
391 assert!(profile.variables.contains_key("NEW_VAR"));
392 }
393
394 #[test]
395 fn test_switch_profile() {
396 let (mut manager, _temp) = create_test_profile_manager();
397
398 manager.create("dev".to_string(), None).unwrap();
399 manager.create("prod".to_string(), None).unwrap();
400
401 assert!(manager.config.active.is_none());
402
403 let result = manager.switch("dev");
404 assert!(result.is_ok());
405 assert_eq!(manager.config.active, Some("dev".to_string()));
406
407 let result = manager.switch("prod");
408 assert!(result.is_ok());
409 assert_eq!(manager.config.active, Some("prod".to_string()));
410 }
411
412 #[test]
413 fn test_switch_to_nonexistent_profile() {
414 let (mut manager, _temp) = create_test_profile_manager();
415
416 let result = manager.switch("nonexistent");
417 assert!(result.is_err());
418 assert!(result.unwrap_err().to_string().contains("not found"));
419 }
420
421 #[test]
422 fn test_active_profile() {
423 let (mut manager, _temp) = create_test_profile_manager();
424
425 assert!(manager.active().is_none());
426
427 manager.create("dev".to_string(), None).unwrap();
428 manager.switch("dev").unwrap();
429
430 let active = manager.active();
431 assert!(active.is_some());
432 assert_eq!(active.unwrap().name, "dev");
433 }
434
435 #[test]
436 fn test_apply_profile() {
437 let (mut manager, _temp) = create_test_profile_manager();
438 let mut env_manager = EnvVarManager::new();
439
440 manager.create("dev".to_string(), None).unwrap();
442 let profile = manager.get_mut("dev").unwrap();
443 profile.add_var("NODE_ENV".to_string(), "development".to_string(), false);
444 profile.add_var("DEBUG".to_string(), "true".to_string(), false);
445
446 let result = manager.apply("dev", &mut env_manager);
447 assert!(result.is_ok());
448
449 assert_eq!(env_manager.get("NODE_ENV").unwrap().value, "development");
451 assert_eq!(env_manager.get("DEBUG").unwrap().value, "true");
452 }
453
454 #[test]
455 fn test_apply_profile_with_disabled_var() {
456 let (mut manager, _temp) = create_test_profile_manager();
457 let mut env_manager = EnvVarManager::new();
458
459 manager.create("dev".to_string(), None).unwrap();
460 let profile = manager.get_mut("dev").unwrap();
461 profile.variables.insert(
462 "DISABLED_VAR".to_string(),
463 ProfileVar {
464 value: "should_not_be_set".to_string(),
465 enabled: false,
466 override_system: false,
467 },
468 );
469 profile.add_var("ENABLED_VAR".to_string(), "should_be_set".to_string(), false);
470
471 manager.apply("dev", &mut env_manager).unwrap();
472
473 assert!(env_manager.get("DISABLED_VAR").is_none());
474 assert_eq!(env_manager.get("ENABLED_VAR").unwrap().value, "should_be_set");
475 }
476
477 #[test]
478 fn test_apply_profile_with_parent() {
479 let (mut manager, _temp) = create_test_profile_manager();
480 let mut env_manager = EnvVarManager::new();
481
482 manager.create("base".to_string(), None).unwrap();
484 let profile = manager.get_mut("base").unwrap();
485 profile.add_var("BASE_VAR".to_string(), "base_value".to_string(), false);
486 profile.add_var("OVERRIDE_ME".to_string(), "base_override".to_string(), false);
487
488 manager.create("dev".to_string(), None).unwrap();
490 let profile = manager.get_mut("dev").unwrap();
491 profile.parent = Some("base".to_string());
492 profile.add_var("DEV_VAR".to_string(), "dev_value".to_string(), false);
493 profile.add_var("OVERRIDE_ME".to_string(), "dev_override".to_string(), false);
494
495 manager.apply("dev", &mut env_manager).unwrap();
496
497 assert_eq!(env_manager.get("BASE_VAR").unwrap().value, "base_value");
499 assert_eq!(env_manager.get("DEV_VAR").unwrap().value, "dev_value");
500 assert_eq!(env_manager.get("OVERRIDE_ME").unwrap().value, "dev_override");
502 }
503
504 #[test]
505 fn test_apply_nonexistent_profile() {
506 let (manager, _temp) = create_test_profile_manager();
507 let mut env_manager = EnvVarManager::new();
508
509 let result = manager.apply("nonexistent", &mut env_manager);
510 assert!(result.is_err());
511 assert!(result.unwrap_err().to_string().contains("not found"));
512 }
513
514 #[test]
515 fn test_export_profile() {
516 let (mut manager, _temp) = create_test_profile_manager();
517
518 manager
519 .create("dev".to_string(), Some("Development".to_string()))
520 .unwrap();
521 let profile = manager.get_mut("dev").unwrap();
522 profile.add_var("TEST_VAR".to_string(), "test_value".to_string(), false);
523
524 let result = manager.export("dev");
525 assert!(result.is_ok());
526
527 let json = result.unwrap();
528 assert!(json.contains("\"name\": \"dev\""));
529 assert!(json.contains("\"description\": \"Development\""));
530 assert!(json.contains("TEST_VAR"));
531 assert!(json.contains("test_value"));
532 }
533
534 #[test]
535 fn test_export_nonexistent_profile() {
536 let (manager, _temp) = create_test_profile_manager();
537
538 let result = manager.export("nonexistent");
539 assert!(result.is_err());
540 assert!(result.unwrap_err().to_string().contains("not found"));
541 }
542
543 #[test]
544 fn test_import_profile() {
545 let (mut manager, _temp) = create_test_profile_manager();
546
547 let profile_json = r#"{
548 "name": "imported",
549 "description": "Imported profile",
550 "created_at": "2024-01-01T00:00:00Z",
551 "updated_at": "2024-01-01T00:00:00Z",
552 "variables": {
553 "IMPORT_VAR": {
554 "value": "imported_value",
555 "enabled": true,
556 "override_system": false
557 }
558 },
559 "parent": null,
560 "metadata": {}
561 }"#;
562
563 let result = manager.import("new_name".to_string(), profile_json, false);
564 assert!(result.is_ok());
565
566 let profile = manager.get("new_name").unwrap();
567 assert_eq!(profile.name, "new_name"); assert_eq!(profile.description, Some("Imported profile".to_string()));
569 assert!(profile.variables.contains_key("IMPORT_VAR"));
570 }
571
572 #[test]
573 fn test_import_profile_overwrite() {
574 let (mut manager, _temp) = create_test_profile_manager();
575
576 manager.create("existing".to_string(), None).unwrap();
577
578 let profile_json = r#"{
579 "name": "imported",
580 "description": "New description",
581 "created_at": "2024-01-01T00:00:00Z",
582 "updated_at": "2024-01-01T00:00:00Z",
583 "variables": {},
584 "parent": null,
585 "metadata": {}
586 }"#;
587
588 let result = manager.import("existing".to_string(), profile_json, false);
590 assert!(result.is_err());
591 assert!(result.unwrap_err().to_string().contains("already exists"));
592
593 let result = manager.import("existing".to_string(), profile_json, true);
595 assert!(result.is_ok());
596
597 let profile = manager.get("existing").unwrap();
598 assert_eq!(profile.description, Some("New description".to_string()));
599 }
600
601 #[test]
602 fn test_import_invalid_json() {
603 let (mut manager, _temp) = create_test_profile_manager();
604
605 let invalid_json = "{ invalid json }";
606
607 let result = manager.import("test".to_string(), invalid_json, false);
608 assert!(result.is_err());
609 }
610
611 #[test]
612 fn test_save_and_load() {
613 let temp_dir = TempDir::new().unwrap();
614 let config_path = temp_dir.path().join("profiles.json");
615
616 {
618 let mut manager = ProfileManager {
619 config_path: config_path.clone(),
620 config: ProfileConfig {
621 active: None,
622 profiles: HashMap::new(),
623 },
624 };
625
626 manager.create("dev".to_string(), None).unwrap();
627 manager.create("prod".to_string(), None).unwrap();
628 manager.switch("dev").unwrap();
629
630 let result = manager.save();
631 assert!(result.is_ok());
632 }
633
634 {
636 assert!(config_path.exists());
637
638 let manager = ProfileManager {
639 config_path: config_path.clone(),
640 config: {
641 let content = fs::read_to_string(&config_path).unwrap();
642 serde_json::from_str(&content).unwrap()
643 },
644 };
645
646 assert_eq!(manager.config.profiles.len(), 2);
647 assert_eq!(manager.config.active, Some("dev".to_string()));
648 assert!(manager.get("dev").is_some());
649 assert!(manager.get("prod").is_some());
650 }
651 }
652
653 #[test]
654 fn test_profile_manager_thread_safety() {
655 let (mut manager, _temp) = create_test_profile_manager();
659
660 manager.create("test".to_string(), None).unwrap();
661 let profile = manager.get("test");
662 assert!(profile.is_some());
663
664 let profile_mut = manager.get_mut("test");
666 assert!(profile_mut.is_some());
667 }
668}