ferron/util/
load_config.rs1use 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 let canonical_pathbuf = fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
19
20 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 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 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 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(); if yaml_config.is_hash() {
64 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 if let Some(yaml_config_hash) = yaml_config.as_mut_hash() {
123 yaml_config_hash.remove(&Yaml::String("include".to_string()));
124
125 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 Ok(yaml_config)
173}