vdev/testing/
state.rs

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    /// Check if the named environment is active. If the current config could not be loaded or a
38    /// different environment is active, an error is returned.
39    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    /// Return the currently active environment name.
50    pub fn active(&self) -> Result<Option<String>> {
51        self.load().map(|state| state.map(|config| config.active))
52    }
53
54    /// Load the currently active state data.
55    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}