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