1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::{
    collections::HashMap,
    fmt::Display,
    io::Write,
    ops::{Deref, DerefMut},
    path::PathBuf,
};

use serde::{Deserialize, Serialize};

use crate::{cookie::CookieJar, endpoint::Headers, Ctx, PairMap};

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Variables(pub HashMap<String, String>);

impl Deref for Variables {
    type Target = HashMap<String, String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Variables {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Display for Variables {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (key, value) in self.iter() {
            writeln!(f, "{key}={value}")?;
        }

        Ok(())
    }
}

impl PairMap<'_> for Variables {
    const NAME: &'static str = "variable";

    fn map(&mut self) -> &mut HashMap<String, String> {
        &mut self.0
    }
}

impl Variables {
    pub fn parse(file_content: &str) -> Self {
        let mut variables = Variables::default();

        for var in file_content.split('\n').filter(|line| !line.is_empty()) {
            variables.set(var);
        }

        variables
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Env {
    pub name: String,
    pub variables: Variables,
    pub headers: Headers,
}

impl Default for Env {
    fn default() -> Self {
        Self {
            name: String::from("default"),
            variables: Variables::default(),
            headers: Headers::default(),
        }
    }
}

impl Env {
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            ..Default::default()
        }
    }

    pub fn dir(&self, ctx: &Ctx) -> PathBuf {
        ctx.path().join("env").join(&self.name)
    }

    pub fn write(&self, ctx: &Ctx) -> Result<(), Box<dyn std::error::Error>> {
        let dir = self.dir(ctx);

        std::fs::create_dir(dir)?;

        self.update(ctx)?;

        Ok(())
    }

    pub fn update(&self, ctx: &Ctx) -> Result<(), Box<dyn std::error::Error>> {
        let mut var_file = std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(self.dir(ctx).join("variables"))?;
        let mut headers_file = std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(self.dir(ctx).join("headers"))?;

        if !self.variables.is_empty() {
            var_file.write_all(format!("{}", self.variables).as_bytes())?;
        }
        if !self.headers.0.is_empty() {
            headers_file.write_all(format!("{}", self.headers).as_bytes())?;
        }

        Ok(())
    }

    /// Returns `true` if this environment already exists on the quartz project.
    pub fn exists(&self, ctx: &Ctx) -> bool {
        self.dir(ctx).exists()
    }

    pub fn parse(ctx: &Ctx, name: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let mut env = Self::new(name);

        if let Ok(var_contents) = std::fs::read_to_string(env.dir(ctx).join("variables")) {
            env.variables = Variables::parse(&var_contents);
        }
        if let Ok(header_contents) = std::fs::read_to_string(env.dir(ctx).join("headers")) {
            env.headers = Headers::parse(&header_contents);
        }

        Ok(env)
    }

    pub fn cookie_jar(&self, ctx: &Ctx) -> CookieJar {
        let path = self.dir(ctx).join(CookieJar::FILENAME);
        let mut jar = CookieJar::read(&path).unwrap_or_default();

        jar.path = path;

        jar
    }
}