1use std::{
2 fs,
3 io::ErrorKind,
4 path::{Path, PathBuf},
5 sync::LazyLock,
6};
7
8use anyhow::{Context, Result, anyhow};
9use serde::{Deserialize, Serialize};
10
11use crate::{environment::Environment, platform, util};
12
13static DATA_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
14 [platform::data_dir(), Path::new("integration")]
15 .into_iter()
16 .collect()
17});
18
19#[derive(Debug)]
20pub struct EnvsDir {
21 path: PathBuf,
22}
23
24#[derive(Deserialize, Serialize)]
25pub struct State {
26 pub active: String,
27 pub config: Environment,
28}
29
30impl EnvsDir {
31 pub fn new(integration: &str) -> Self {
32 let config = format!("{integration}.json");
33 let path = [&DATA_DIR, Path::new(&config)].iter().collect();
34 Self { path }
35 }
36
37 pub fn check_active(&self, name: &str) -> Result<bool> {
40 match self.active()? {
41 None => Ok(false),
42 Some(active) if active == name => Ok(true),
43 Some(active) => Err(anyhow!(
44 "Requested environment {name:?} does not match active one {active:?}"
45 )),
46 }
47 }
48
49 pub fn active(&self) -> Result<Option<String>> {
51 self.load().map(|state| state.map(|config| config.active))
52 }
53
54 pub fn load(&self) -> Result<Option<State>> {
56 let json = match fs::read_to_string(&self.path) {
57 Ok(json) => json,
58 Err(error) if error.kind() == ErrorKind::NotFound => return Ok(None),
59 Err(error) => {
60 return Err(error)
61 .context(format!("Could not read state file {}", self.path.display()));
62 }
63 };
64 let state: State = serde_json::from_str(&json)
65 .with_context(|| format!("Could not parse state file {}", self.path.display()))?;
66 Ok(Some(state))
67 }
68
69 pub fn save(&self, environment: &str, config: &Environment) -> Result<()> {
70 let config = State {
71 active: environment.into(),
72 config: config.clone(),
73 };
74 let path = &*DATA_DIR;
75 if !path.is_dir() {
76 fs::create_dir_all(path)
77 .with_context(|| format!("failed to create directory {}", path.display()))?;
78 }
79
80 let config = serde_json::to_string(&config)?;
81 fs::write(&self.path, config)
82 .with_context(|| format!("failed to write file {}", self.path.display()))
83 }
84
85 pub fn remove(&self) -> Result<()> {
86 if util::exists(&self.path)? {
87 fs::remove_file(&self.path)
88 .with_context(|| format!("failed to remove {}", self.path.display()))?;
89 }
90
91 Ok(())
92 }
93}