diff --git a/.gitignore b/.gitignore index 87af617..146633d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ target #.idea/ # generated environment variables file +.env .env.generated + +generated-config.yaml diff --git a/apps/container/Cargo.toml b/apps/container/Cargo.toml index 6b53abc..bc502e1 100644 --- a/apps/container/Cargo.toml +++ b/apps/container/Cargo.toml @@ -15,3 +15,4 @@ tokio = { version = "1.47.0", features = ["full"] } url = "2.5.7" clap = { version = "4.5.48", features = ["derive", "env"] } path-clean = "1.0.1" +serde_json = "1.0.145" diff --git a/apps/container/src/env.rs b/apps/container/src/env.rs index ec1822c..162883f 100644 --- a/apps/container/src/env.rs +++ b/apps/container/src/env.rs @@ -13,24 +13,151 @@ pub struct EnvFile { pub file_type: EnvFileType, pub db_type: DBType, pub db_url: String, + // + buffer: serde_json::Value, } impl EnvFile { - pub fn write(self, path: impl AsRef) { - let path_ref = path.as_ref(); - println!("Config file path: {}", path_ref.display()); - let mut config_file = - std::fs::File::create(path_ref).expect("Failed to create config file"); - // - self._write_line(&mut config_file, "DB_TYPE", &self.db_type.to_string()); - self._write_line(&mut config_file, "DATABASE_URL", &self.db_url.to_string()) + pub fn new(file_type: EnvFileType, db_type: DBType, db_url: String) -> Self { + let mut env_file = EnvFile { + file_type, + db_type, + db_url, + buffer: serde_json::Value::Object(serde_json::Map::new()), + }; + + env_file._write_line_buffer("DATABASE__TYPE", &env_file.db_type.to_string()); + env_file._write_line_buffer("DATABASE__URL", &env_file.db_url.to_string()); + + env_file } - fn _write_line(&self, file: &mut std::fs::File, key: &str, value: &str) { - match self.file_type { - EnvFileType::DotEnv => writeln!(file, "{}={}", key, value), - EnvFileType::Yaml => writeln!(file, "{}: \"{}\"", key, value), + pub fn write(&mut self, stream: &mut dyn Write, with_prefix: bool) { + self._write_buffer(stream, with_prefix); + } + + fn key_into_buffer_key(&self, key: &str) -> Vec { + key.split("__").map(String::from).collect() + } + + fn _write_line_buffer(&mut self, key: &str, value: &str) { + let buffer_key = self.key_into_buffer_key(key); + let mut current = &mut self.buffer; + for k in &buffer_key[0..(buffer_key.len() - 1)] { + if current.get(k).is_none() { + current[k] = serde_json::Value::Object(serde_json::Map::new()); + } + current = &mut current[k]; } - .expect("Failed to write to config file"); + current[buffer_key.last().unwrap()] = serde_json::Value::String(value.to_string()); + } + + fn _write_buffer(&self, file: &mut dyn Write, with_prefix: bool) { + match self.file_type { + EnvFileType::DotEnv => self._write_buffer_env(file, with_prefix), + EnvFileType::Yaml => self._write_buffer_yaml(file), + } + } + + fn _write_buffer_env(&self, file: &mut dyn Write, with_prefix: bool) { + fn _write_buffer_env_layer( + file: &mut dyn Write, + buffer: &serde_json::Value, + prefix: String, + with_root_prefix: bool, + ) { + if let serde_json::Value::Object(map) = buffer { + for (key, value) in map { + let current_key = if prefix.is_empty() { + if with_root_prefix { + format!("YANPM__{}", key) + } else { + key.to_string() + } + } else { + format!("{}__{}", prefix, key) + }; + match value { + serde_json::Value::Object(_) => { + _write_buffer_env_layer(file, value, current_key, with_root_prefix); + } + _ => { + writeln!(file, "{}={}", current_key, value).unwrap(); + } + } + } + } + } + + _write_buffer_env_layer(file, &self.buffer, String::new(), with_prefix); + } + + fn _write_buffer_yaml(&self, file: &mut dyn Write) { + let mut layer = 0; + fn _write_buffer_yaml_layer( + file: &mut dyn Write, + buffer: &serde_json::Value, + layer: &mut usize, + ) { + if let serde_json::Value::Object(map) = buffer { + for (key, value) in map { + let indent = " ".repeat(*layer); + match value { + serde_json::Value::Object(_) => { + writeln!(file, "{}{}:", indent, key).unwrap(); + *layer += 1; + _write_buffer_yaml_layer(file, value, layer); + *layer -= 1; + } + _ => { + writeln!(file, "{}{}: {}", indent, key, value).unwrap(); + } + } + } + } + } + + _write_buffer_yaml_layer(file, &self.buffer, &mut layer); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_env_file_write_yaml() { + let mut env_file_nested = EnvFile::new( + EnvFileType::Yaml, + DBType::SQLite, + "mysql://user:pass@localhost/db".to_string(), + ); + + let mut output_stream = Vec::new(); + env_file_nested.write(&mut output_stream, false); + let output_string = String::from_utf8(output_stream).unwrap(); + let expected_output = "\ +DATABASE: + TYPE: \"SQLite\" + URL: \"mysql://user:pass@localhost/db\" +"; + assert_eq!(output_string, expected_output); + } + + #[test] + fn test_env_file_write_env() { + let mut env_file_nested = EnvFile::new( + EnvFileType::DotEnv, + DBType::PostgreSQL, + "postgres://user:pass@localhost/db".to_string(), + ); + let mut output_stream = Vec::new(); + env_file_nested.write(&mut output_stream, true); + let output_string = String::from_utf8(output_stream).unwrap(); + let expected_output = "\ +YANPM__DATABASE__TYPE=\"PostgreSQL\" +YANPM__DATABASE__URL=\"postgres://user:pass@localhost/db\" +"; + assert_eq!(output_string, expected_output); } } diff --git a/apps/container/src/util.rs b/apps/container/src/util.rs index 9783ae1..d513904 100644 --- a/apps/container/src/util.rs +++ b/apps/container/src/util.rs @@ -29,17 +29,18 @@ pub fn write_env_files(db_config: &DBConfigInfoType) { DBConfigInfoType::PreExisting(config) => (config.db_type.clone(), config.url.clone()), }; - let api_env_file = EnvFile { - file_type: env::EnvFileType::Yaml, - db_type, - db_url, - }; + let mut api_env = EnvFile::new(env::EnvFileType::Yaml, db_type, db_url); + let mut db_env = api_env.clone(); + db_env.file_type = env::EnvFileType::DotEnv; - let mut db_env_file = api_env_file.clone(); - db_env_file.file_type = env::EnvFileType::DotEnv; + let mut api_file = + std::fs::File::create(&api_config_path_absolute).expect("Failed to create API config file"); - api_env_file.write(&api_config_path_absolute); - db_env_file.write(&db_config_path_absolute); + let mut db_file = + std::fs::File::create(&db_config_path_absolute).expect("Failed to create DB config file"); + + api_env.write(&mut api_file, true); + db_env.write(&mut db_file, false); } pub async fn stop_container(