dcd/composer/variables/
availability.rs1use 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 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 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 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 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 if std::env::var(&var.name).is_ok() {
104 status.available_in_system.push(var.name.clone());
105 return Ok(());
106 }
107
108 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 if var.default_value.is_some() {
116 status.available_from_defaults.push(var.name.clone());
117 return Ok(());
118 }
119
120 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 pub fn get_available_variables(&self) -> HashMap<String, String> {
132 let mut available = HashMap::new();
133
134 for (key, value) in std::env::vars() {
136 available.insert(key, value);
137 }
138
139 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}