ferron/util/
combine_config.rs1use std::{net::IpAddr, sync::Arc};
2
3use yaml_rust2::{yaml::Hash, Yaml};
4
5use crate::ferron_util::{
6 ip_match::ip_match, match_hostname::match_hostname, match_location::match_location,
7};
8
9pub fn combine_config(
10 config: Arc<Yaml>,
11 hostname: Option<&str>,
12 client_ip: IpAddr,
13 path: &str,
14) -> Option<Yaml> {
15 let global_config = config["global"].as_hash();
16 let combined_config = global_config.cloned();
17
18 if let Some(host_config) = config["hosts"].as_vec() {
19 for host in host_config {
20 if let Some(host_hashtable) = host.as_hash() {
21 let domain_matched = host_hashtable
22 .get(&Yaml::String("domain".to_string()))
23 .and_then(Yaml::as_str)
24 .map(|domain| match_hostname(Some(domain), hostname))
25 .unwrap_or(true);
26
27 let ip_matched = host_hashtable
28 .get(&Yaml::String("ip".to_string()))
29 .and_then(Yaml::as_str)
30 .map(|ip| ip_match(ip, client_ip))
31 .unwrap_or(true);
32
33 if domain_matched && ip_matched {
34 return Some(merge_host_configs(combined_config, host_hashtable, path));
35 }
36 }
37 }
38 }
39
40 combined_config.map(Yaml::Hash)
41}
42
43fn merge_host_configs(global: Option<Hash>, host: &Hash, path: &str) -> Yaml {
44 let mut merged = global.unwrap_or_default();
45 let mut locations = None;
46
47 for (key, value) in host {
48 if let Some(key) = key.as_str() {
49 if key == "locations" {
50 if let Some(obtained_locations) = value.as_vec() {
51 locations = Some(obtained_locations);
52 }
53 } else {
54 match value {
55 Yaml::Array(host_array) => {
56 merged
57 .entry(Yaml::String(key.to_string()))
58 .and_modify(|global_val| {
59 if let Yaml::Array(global_array) = global_val {
60 global_array.extend(host_array.clone());
61 } else {
62 *global_val = Yaml::Array(host_array.clone());
63 }
64 })
65 .or_insert_with(|| Yaml::Array(host_array.clone()));
66 }
67 Yaml::Hash(host_hash) => {
68 merged
69 .entry(Yaml::String(key.to_string()))
70 .and_modify(|global_val| {
71 if let Yaml::Hash(global_hash) = global_val {
72 for (k, v) in host_hash {
73 global_hash.insert(k.clone(), v.clone());
74 }
75 } else {
76 *global_val = Yaml::Hash(host_hash.clone());
77 }
78 })
79 .or_insert_with(|| Yaml::Hash(host_hash.clone()));
80 }
81 _ => {
82 merged.insert(Yaml::String(key.to_string()), value.clone());
83 }
84 }
85 }
86 }
87 }
88
89 if let Some(locations) = locations {
90 if let Ok(decoded_path) = urlencoding::decode(path) {
91 for location in locations {
92 if let Some(location_hashtable) = location.as_hash() {
93 let path_matched = location_hashtable
94 .get(&Yaml::String("path".to_string()))
95 .and_then(Yaml::as_str)
96 .map(|path_match| match_location(path_match, &decoded_path))
97 .unwrap_or(true);
98
99 if path_matched {
100 return merge_location_configs(Some(merged), location_hashtable);
101 }
102 }
103 }
104 }
105 }
106
107 Yaml::Hash(merged)
108}
109
110fn merge_location_configs(global: Option<Hash>, location: &Hash) -> Yaml {
111 let mut merged = global.unwrap_or_default();
112
113 for (key, value) in location {
114 if let Some(key) = key.as_str() {
115 match value {
116 Yaml::Array(host_array) => {
117 merged
118 .entry(Yaml::String(key.to_string()))
119 .and_modify(|global_val| {
120 if let Yaml::Array(global_array) = global_val {
121 global_array.extend(host_array.clone());
122 } else {
123 *global_val = Yaml::Array(host_array.clone());
124 }
125 })
126 .or_insert_with(|| Yaml::Array(host_array.clone()));
127 }
128 Yaml::Hash(host_hash) => {
129 merged
130 .entry(Yaml::String(key.to_string()))
131 .and_modify(|global_val| {
132 if let Yaml::Hash(global_hash) = global_val {
133 for (k, v) in host_hash {
134 global_hash.insert(k.clone(), v.clone());
135 }
136 } else {
137 *global_val = Yaml::Hash(host_hash.clone());
138 }
139 })
140 .or_insert_with(|| Yaml::Hash(host_hash.clone()));
141 }
142 _ => {
143 merged.insert(Yaml::String(key.to_string()), value.clone());
144 }
145 }
146 }
147 }
148
149 Yaml::Hash(merged)
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use std::net::{IpAddr, Ipv4Addr};
156 use yaml_rust2::{Yaml, YamlLoader};
157
158 fn create_test_config() -> Arc<Yaml> {
159 let yaml_str = r#"
160 global:
161 key1:
162 - global_value1
163 key2:
164 - global_value2
165 hosts:
166 - domain: example.com
167 ip: 192.168.1.1
168 key1:
169 - host_value1
170 key2:
171 - host_value2
172 - domain: test.com
173 ip: 192.168.1.2
174 key3:
175 - host_value3
176 "#;
177
178 let docs = YamlLoader::load_from_str(yaml_str).unwrap();
179 Arc::new(docs[0].clone())
180 }
181
182 #[test]
183 fn test_combine_config_with_matching_hostname_and_ip() {
184 let config = create_test_config();
185 let hostname = Some("example.com");
186 let client_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
187
188 let result = combine_config(config, hostname, client_ip, "/");
189 assert!(result.is_some());
190
191 let result_yaml = result.unwrap();
192 let result_hash = result_yaml.as_hash().unwrap();
193
194 assert_eq!(
195 result_hash
196 .get(&Yaml::String("key1".to_string()))
197 .unwrap()
198 .as_vec()
199 .unwrap()
200 .len(),
201 2
202 );
203 assert_eq!(
204 result_hash
205 .get(&Yaml::String("key2".to_string()))
206 .unwrap()
207 .as_vec()
208 .unwrap()
209 .len(),
210 2
211 );
212 }
213
214 #[test]
215 fn test_combine_config_with_non_matching_hostname() {
216 let config = create_test_config();
217 let hostname = Some("nonexistent.com");
218 let client_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
219
220 let result = combine_config(config, hostname, client_ip, "/");
221 assert!(result
222 .unwrap()
223 .as_hash()
224 .unwrap()
225 .get(&Yaml::String(String::from("key3")))
226 .is_none());
227 }
228
229 #[test]
230 fn test_combine_config_with_non_matching_ip() {
231 let config = create_test_config();
232 let hostname = Some("example.com");
233 let client_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
234
235 let result = combine_config(config, hostname, client_ip, "/");
236 assert!(result
237 .unwrap()
238 .as_hash()
239 .unwrap()
240 .get(&Yaml::String(String::from("key3")))
241 .is_none());
242 }
243
244 #[test]
245 fn test_combine_config_with_global_only() {
246 let yaml_str = r#"
247 global:
248 key1: value1
249 key2:
250 - global_value2
251 hosts: []
252 "#;
253
254 let docs = YamlLoader::load_from_str(yaml_str).unwrap();
255 let config = Arc::new(docs[0].clone());
256 let hostname = None;
257 let client_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
258
259 let result = combine_config(config, hostname, client_ip, "/");
260 assert!(result.is_some());
261
262 let result_yaml = result.unwrap();
263 let result_hash = result_yaml.as_hash().unwrap();
264
265 assert_eq!(
266 result_hash
267 .get(&Yaml::String("key1".to_string()))
268 .unwrap()
269 .as_str()
270 .unwrap(),
271 "value1"
272 );
273 assert_eq!(
274 result_hash
275 .get(&Yaml::String("key2".to_string()))
276 .unwrap()
277 .as_vec()
278 .unwrap()
279 .len(),
280 1
281 );
282 }
283
284 #[test]
285 fn test_combine_config_with_empty_host_config() {
286 let yaml_str = r#"
287 global:
288 key1: value1
289 key2:
290 - global_value2
291 hosts: []
292 "#;
293
294 let docs = YamlLoader::load_from_str(yaml_str).unwrap();
295 let config_yaml = docs[0].clone();
296
297 let hostname = Some("example.com");
298 let client_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
299
300 let result = combine_config(Arc::new(config_yaml), hostname, client_ip, "/");
301 assert!(result.is_some());
302
303 let result_yaml = result.unwrap();
304
305 assert_eq!(result_yaml["key1"].as_str().unwrap(), "value1");
306 assert_eq!(result_yaml["key2"].as_vec().unwrap().len(), 1);
307 }
308
309 #[test]
310 fn test_combine_config_with_path_match() {
311 let yaml_str = r#"
312 global:
313 key1:
314 - global_value1
315 key2:
316 - global_value2
317 hosts:
318 - domain: example.com
319 ip: 192.168.1.1
320 locations:
321 - path: /test
322 key3:
323 - location_value
324 "#;
325
326 let docs = YamlLoader::load_from_str(yaml_str).unwrap();
327 let config_yaml = docs[0].clone();
328
329 let hostname = Some("example.com");
330 let client_ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
331
332 let result = combine_config(Arc::new(config_yaml), hostname, client_ip, "/test");
333 assert!(result.is_some());
334
335 let result_yaml = result.unwrap();
336
337 assert_eq!(result_yaml["key3"].as_vec().unwrap().len(), 1);
338 }
339}