ferron/util/
load_config.rs

1use std::fs;
2use std::path::PathBuf;
3use std::str::FromStr;
4use std::{collections::HashSet, error::Error};
5
6use glob::glob;
7use yaml_rust2::{Yaml, YamlLoader};
8// Import env_config for environment variable overrides
9use crate::ferron_util::env_config;
10
11pub fn load_config(path: PathBuf) -> Result<Yaml, Box<dyn Error + Send + Sync>> {
12  let mut yaml_config = load_config_inner(path, &mut HashSet::new())?;
13  // Apply FERRON_ environment variable overrides
14  env_config::apply_env_vars_to_config(&mut yaml_config);
15  Ok(yaml_config)
16}
17
18fn load_config_inner(
19  path: PathBuf,
20  loaded_paths: &mut HashSet<PathBuf>,
21) -> Result<Yaml, Box<dyn Error + Send + Sync>> {
22  // Canonicalize the path
23  let canonical_pathbuf = fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
24
25  // Check if the path is duplicate. If it's not, add it to loaded paths.
26  if loaded_paths.contains(&canonical_pathbuf) {
27    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
28
29    Err(anyhow::anyhow!(
30      "Detected the server configuration file include loop while attempting to load \"{}\"",
31      canonical_path
32    ))?
33  } else {
34    loaded_paths.insert(canonical_pathbuf.clone());
35  }
36
37  // Read the configuration file
38  let file_contents = match fs::read_to_string(&path) {
39    Ok(file) => file,
40    Err(err) => {
41      let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
42
43      Err(anyhow::anyhow!(
44        "Failed to read from the server configuration file at \"{}\": {}",
45        canonical_path,
46        err
47      ))?
48    }
49  };
50
51  // Load YAML configuration from the file contents
52  let yaml_configs = match YamlLoader::load_from_str(&file_contents) {
53    Ok(yaml_configs) => yaml_configs,
54    Err(err) => Err(anyhow::anyhow!(
55      "Failed to parse the server configuration file: {}",
56      err
57    ))?,
58  };
59
60  // Ensure the YAML file is not empty
61  if yaml_configs.is_empty() {
62    Err(anyhow::anyhow!(
63      "No YAML documents detected in the server configuration file."
64    ))?;
65  }
66  let mut yaml_config = yaml_configs[0].clone(); // Clone the first YAML document
67
68  if yaml_config.is_hash() {
69    // Get the list of included files
70    let mut include_files = Vec::new();
71    if let Some(include_yaml) = yaml_config["include"].as_vec() {
72      for include_one_yaml in include_yaml.iter() {
73        if let Some(include_glob) = include_one_yaml.as_str() {
74          let include_glob_pathbuf = match PathBuf::from_str(include_glob) {
75            Ok(pathbuf) => pathbuf,
76            Err(err) => {
77              let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
78
79              Err(anyhow::anyhow!(
80                "Failed to determine includes for the server configuration file at \"{}\": {}",
81                canonical_path,
82                err
83              ))?
84            }
85          };
86          let include_glob_pathbuf_canonicalized = if include_glob_pathbuf.is_absolute() {
87            include_glob_pathbuf
88          } else {
89            let mut canonical_dirname = canonical_pathbuf.clone();
90            canonical_dirname.pop();
91            canonical_dirname.join(include_glob_pathbuf)
92          };
93          let files_globbed = match glob(&include_glob_pathbuf_canonicalized.to_string_lossy()) {
94            Ok(files_globbed) => files_globbed,
95            Err(err) => {
96              let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
97
98              Err(anyhow::anyhow!(
99                "Failed to determine includes for the server configuration file at \"{}\": {}",
100                canonical_path,
101                err
102              ))?
103            }
104          };
105
106          for file_globbed_result in files_globbed {
107            let file_globbed = match file_globbed_result {
108              Ok(file_globbed) => file_globbed,
109              Err(err) => {
110                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
111
112                Err(anyhow::anyhow!(
113                  "Failed to determine includes for the server configuration file at \"{}\": {}",
114                  canonical_path,
115                  err
116                ))?
117              }
118            };
119            include_files
120              .push(fs::canonicalize(&file_globbed).unwrap_or_else(|_| file_globbed.clone()));
121          }
122        }
123      }
124    }
125
126    // Delete included configuration from YAML configuration
127    if let Some(yaml_config_hash) = yaml_config.as_mut_hash() {
128      yaml_config_hash.remove(&Yaml::String("include".to_string()));
129
130      // Merge included configuration
131      for included_file in include_files {
132        let yaml_to_include = load_config_inner(included_file, loaded_paths)?;
133        if let Some(yaml_to_include_hashmap) = yaml_to_include.as_hash() {
134          for (key, value) in yaml_to_include_hashmap.iter() {
135            if let Some(key) = key.as_str() {
136              if key != "include" {
137                match value {
138                  Yaml::Array(host_array) => {
139                    yaml_config_hash
140                      .entry(Yaml::String(key.to_string()))
141                      .and_modify(|global_val| {
142                        if let Yaml::Array(global_array) = global_val {
143                          global_array.extend(host_array.clone());
144                        } else {
145                          *global_val = Yaml::Array(host_array.clone());
146                        }
147                      })
148                      .or_insert_with(|| Yaml::Array(host_array.clone()));
149                  }
150                  Yaml::Hash(host_hash) => {
151                    yaml_config_hash
152                      .entry(Yaml::String(key.to_string()))
153                      .and_modify(|global_val| {
154                        if let Yaml::Hash(global_hash) = global_val {
155                          for (k, v) in host_hash {
156                            global_hash.insert(k.clone(), v.clone());
157                          }
158                        } else {
159                          *global_val = Yaml::Hash(host_hash.clone());
160                        }
161                      })
162                      .or_insert_with(|| Yaml::Hash(host_hash.clone()));
163                  }
164                  _ => {
165                    yaml_config_hash.insert(Yaml::String(key.to_string()), value.clone());
166                  }
167                }
168              }
169            }
170          }
171        }
172      }
173    }
174  }
175
176  // Return the server configuration
177  Ok(yaml_config)
178}