dcd/composer/variables/
availability.rs

1use super::validator::VariablesValidator;
2use crate::composer::types::{ComposerResult, ComposerVariables};
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct EnvironmentStatus {
8    pub available_in_system: Vec<String>,
9    pub available_in_env_file: Vec<String>,
10    pub available_from_defaults: Vec<String>,
11    pub missing_required: Vec<String>,
12    pub missing_optional: Vec<String>,
13}
14
15impl Default for EnvironmentStatus {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl EnvironmentStatus {
22    pub fn new() -> Self {
23        Self {
24            available_in_system: Vec::new(),
25            available_in_env_file: Vec::new(),
26            available_from_defaults: Vec::new(),
27            missing_required: Vec::new(),
28            missing_optional: Vec::new(),
29        }
30    }
31
32    pub fn get_resolved_variables(&self) -> HashMap<String, String> {
33        let mut resolved = HashMap::new();
34
35        // Add system environment variables
36        for var_name in &self.available_in_system {
37            if let Ok(value) = std::env::var(var_name) {
38                resolved.insert(var_name.clone(), value);
39            }
40        }
41
42        // Add .env file variables
43        for var_name in &self.available_in_env_file {
44            if let Ok(value) = std::env::var(var_name) {
45                resolved.insert(var_name.clone(), value);
46            }
47        }
48
49        // Add default values
50        for var_name in &self.available_from_defaults {
51            if let Ok(value) = std::env::var(var_name) {
52                resolved.insert(var_name.clone(), value);
53            }
54        }
55
56        resolved
57    }
58
59    pub fn is_valid(&self) -> bool {
60        self.missing_required.is_empty()
61    }
62}
63
64pub struct EnvironmentChecker {
65    validator: VariablesValidator,
66}
67
68impl Default for EnvironmentChecker {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl EnvironmentChecker {
75    pub fn new() -> Self {
76        Self {
77            validator: VariablesValidator::new(),
78        }
79    }
80
81    /// Check availability of all required variables
82    pub async fn check_environment(
83        &mut self,
84        variables: &[ComposerVariables],
85        env_files: &[PathBuf],
86    ) -> ComposerResult<EnvironmentStatus> {
87        self.validator.load_env_files(env_files)?;
88        let mut status = EnvironmentStatus::new();
89
90        for var in variables {
91            self.check_variable_availability(var, &mut status)?;
92        }
93
94        Ok(status)
95    }
96
97    fn check_variable_availability(
98        &self,
99        var: &ComposerVariables,
100        status: &mut EnvironmentStatus,
101    ) -> ComposerResult<()> {
102        // Check system environment
103        if std::env::var(&var.name).is_ok() {
104            status.available_in_system.push(var.name.clone());
105            return Ok(());
106        }
107
108        // Check .env file
109        if self.validator.has_env_file_variable(&var.name) {
110            status.available_in_env_file.push(var.name.clone());
111            return Ok(());
112        }
113
114        // Check defaults
115        if var.default_value.is_some() {
116            status.available_from_defaults.push(var.name.clone());
117            return Ok(());
118        }
119
120        // Variable is missing
121        if var.required {
122            status.missing_required.push(var.name.clone());
123        } else {
124            status.missing_optional.push(var.name.clone());
125        }
126
127        Ok(())
128    }
129
130    /// Get all available environment variables with their values
131    pub fn get_available_variables(&self) -> HashMap<String, String> {
132        let mut available = HashMap::new();
133
134        // System environment takes precedence
135        for (key, value) in std::env::vars() {
136            available.insert(key, value);
137        }
138
139        // Add .env file variables (don't override system env)
140        for (key, value) in self.validator.get_env_file_variables() {
141            available.entry(key.clone()).or_insert(value.clone());
142        }
143
144        available
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use std::fs::File;
152    use std::io::Write;
153    use tempfile::TempDir;
154
155    fn create_test_env_file(dir: &TempDir, content: &str) -> std::io::Result<()> {
156        let env_path = dir.path().join(".env");
157        let mut file = File::create(env_path)?;
158        write!(file, "{}", content)?;
159        Ok(())
160    }
161
162    #[tokio::test]
163    async fn test_environment_checker() -> ComposerResult<()> {
164        let temp_dir = TempDir::new().unwrap();
165        create_test_env_file(&temp_dir, "ENV_FILE_VAR=value\nDB_PORT=5432").unwrap();
166
167        std::env::set_var("SYSTEM_VAR", "system_value");
168
169        let variables = vec![
170            ComposerVariables {
171                name: "SYSTEM_VAR".to_string(),
172                required: true,
173                default_value: None,
174                alternate_value: None,
175            },
176            ComposerVariables {
177                name: "ENV_FILE_VAR".to_string(),
178                required: true,
179                default_value: None,
180                alternate_value: None,
181            },
182            ComposerVariables {
183                name: "DEFAULT_VAR".to_string(),
184                required: false,
185                default_value: Some("default".to_string()),
186                alternate_value: None,
187            },
188            ComposerVariables {
189                name: "MISSING_REQUIRED".to_string(),
190                required: true,
191                default_value: None,
192                alternate_value: None,
193            },
194            ComposerVariables {
195                name: "MISSING_OPTIONAL".to_string(),
196                required: false,
197                default_value: None,
198                alternate_value: None,
199            },
200        ];
201
202        let mut checker = EnvironmentChecker::new();
203        let status = checker
204            .check_environment(&variables, &[temp_dir.path().join(".env")])
205            .await?;
206
207        assert!(!status.is_valid());
208        assert_eq!(status.available_in_system, vec!["SYSTEM_VAR"]);
209        assert_eq!(status.available_in_env_file, vec!["ENV_FILE_VAR"]);
210        assert_eq!(status.available_from_defaults, vec!["DEFAULT_VAR"]);
211        assert_eq!(status.missing_required, vec!["MISSING_REQUIRED"]);
212        assert_eq!(status.missing_optional, vec!["MISSING_OPTIONAL"]);
213
214        Ok(())
215    }
216}