use std::fs; use std::path::PathBuf; use std::str::FromStr; use std::{collections::HashSet, error::Error}; use glob::glob; use yaml_rust2::{Yaml, YamlLoader}; pub fn load_config(path: PathBuf) -> Result> { load_config_inner(path, &mut HashSet::new()) } fn load_config_inner( path: PathBuf, loaded_paths: &mut HashSet, ) -> Result> { // Canonicalize the path let canonical_pathbuf = fs::canonicalize(&path).unwrap_or_else(|_| path.clone()); // Check if the path is duplicate. If it's not, add it to loaded paths. if loaded_paths.contains(&canonical_pathbuf) { let canonical_path = canonical_pathbuf.to_string_lossy().into_owned(); Err(anyhow::anyhow!( "Detected the server configuration file include loop while attempting to load \"{}\"", canonical_path ))? } else { loaded_paths.insert(canonical_pathbuf.clone()); } // Read the configuration file let file_contents = match fs::read_to_string(&path) { Ok(file) => file, Err(err) => { let canonical_path = canonical_pathbuf.to_string_lossy().into_owned(); Err(anyhow::anyhow!( "Failed to read from the server configuration file at \"{}\": {}", canonical_path, err ))? } }; // Load YAML configuration from the file contents let yaml_configs = match YamlLoader::load_from_str(&file_contents) { Ok(yaml_configs) => yaml_configs, Err(err) => Err(anyhow::anyhow!( "Failed to parse the server configuration file: {}", err ))?, }; // Ensure the YAML file is not empty if yaml_configs.is_empty() { Err(anyhow::anyhow!( "No YAML documents detected in the server configuration file." ))?; } let mut yaml_config = yaml_configs[0].clone(); // Clone the first YAML document if yaml_config.is_hash() { // Get the list of included files let mut include_files = Vec::new(); if let Some(include_yaml) = yaml_config["include"].as_vec() { for include_one_yaml in include_yaml.iter() { if let Some(include_glob) = include_one_yaml.as_str() { let include_glob_pathbuf = match PathBuf::from_str(include_glob) { Ok(pathbuf) => pathbuf, Err(err) => { let canonical_path = canonical_pathbuf.to_string_lossy().into_owned(); Err(anyhow::anyhow!( "Failed to determine includes for the server configuration file at \"{}\": {}", canonical_path, err ))? } }; let include_glob_pathbuf_canonicalized = if include_glob_pathbuf.is_absolute() { include_glob_pathbuf } else { let mut canonical_dirname = canonical_pathbuf.clone(); canonical_dirname.pop(); canonical_dirname.join(include_glob_pathbuf) }; let files_globbed = match glob(&include_glob_pathbuf_canonicalized.to_string_lossy()) { Ok(files_globbed) => files_globbed, Err(err) => { let canonical_path = canonical_pathbuf.to_string_lossy().into_owned(); Err(anyhow::anyhow!( "Failed to determine includes for the server configuration file at \"{}\": {}", canonical_path, err ))? } }; for file_globbed_result in files_globbed { let file_globbed = match file_globbed_result { Ok(file_globbed) => file_globbed, Err(err) => { let canonical_path = canonical_pathbuf.to_string_lossy().into_owned(); Err(anyhow::anyhow!( "Failed to determine includes for the server configuration file at \"{}\": {}", canonical_path, err ))? } }; include_files .push(fs::canonicalize(&file_globbed).unwrap_or_else(|_| file_globbed.clone())); } } } } // Delete included configuration from YAML configuration if let Some(yaml_config_hash) = yaml_config.as_mut_hash() { yaml_config_hash.remove(&Yaml::String("include".to_string())); // Merge included configuration for included_file in include_files { let yaml_to_include = load_config_inner(included_file, loaded_paths)?; if let Some(yaml_to_include_hashmap) = yaml_to_include.as_hash() { for (key, value) in yaml_to_include_hashmap.iter() { if let Some(key) = key.as_str() { if key != "include" { match value { Yaml::Array(host_array) => { yaml_config_hash .entry(Yaml::String(key.to_string())) .and_modify(|global_val| { if let Yaml::Array(global_array) = global_val { global_array.extend(host_array.clone()); } else { *global_val = Yaml::Array(host_array.clone()); } }) .or_insert_with(|| Yaml::Array(host_array.clone())); } Yaml::Hash(host_hash) => { yaml_config_hash .entry(Yaml::String(key.to_string())) .and_modify(|global_val| { if let Yaml::Hash(global_hash) = global_val { for (k, v) in host_hash { global_hash.insert(k.clone(), v.clone()); } } else { *global_val = Yaml::Hash(host_hash.clone()); } }) .or_insert_with(|| Yaml::Hash(host_hash.clone())); } _ => { yaml_config_hash.insert(Yaml::String(key.to_string()), value.clone()); } } } } } } } } } // Return the server configuration Ok(yaml_config) }