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};
8use 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 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 let canonical_pathbuf = fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
24
25 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 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 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 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(); if yaml_config.is_hash() {
69 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 if let Some(yaml_config_hash) = yaml_config.as_mut_hash() {
128 yaml_config_hash.remove(&Yaml::String("include".to_string()));
129
130 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 Ok(yaml_config)
178}