ferron/config/adapters/
yaml_legacy.rs

1use std::{
2  collections::HashMap,
3  error::Error,
4  net::{IpAddr, SocketAddr},
5  path::Path,
6};
7
8use ferron_common::observability::ObservabilityBackendChannels;
9use ferron_yaml2kdl_core::convert_yaml_to_kdl;
10use kdl::{KdlDocument, KdlNode, KdlValue};
11
12use crate::config::{
13  Conditions, ErrorHandlerStatus, ServerConfiguration, ServerConfigurationEntries, ServerConfigurationEntry,
14  ServerConfigurationFilters, ServerConfigurationValue,
15};
16
17use super::ConfigurationAdapter;
18
19fn kdl_node_to_configuration_entry(kdl_node: &KdlNode) -> ServerConfigurationEntry {
20  let mut values = Vec::new();
21  let mut props = HashMap::new();
22  for kdl_entry in kdl_node.iter() {
23    let value = match kdl_entry.value().to_owned() {
24      KdlValue::String(value) => ServerConfigurationValue::String(value),
25      KdlValue::Integer(value) => ServerConfigurationValue::Integer(value),
26      KdlValue::Float(value) => ServerConfigurationValue::Float(value),
27      KdlValue::Bool(value) => ServerConfigurationValue::Bool(value),
28      KdlValue::Null => ServerConfigurationValue::Null,
29    };
30    if let Some(prop_name) = kdl_entry.name() {
31      props.insert(prop_name.value().to_string(), value);
32    } else {
33      values.push(value);
34    }
35  }
36  if values.is_empty() {
37    // If KDL node doesn't have any arguments, add the "#true" KDL value
38    values.push(ServerConfigurationValue::Bool(true));
39  }
40  ServerConfigurationEntry { values, props }
41}
42
43/// A legacy YAML configuration adapter that utilizes `ferron-yaml2kdl-core` component
44pub struct YamlLegacyConfigurationAdapter;
45
46impl YamlLegacyConfigurationAdapter {
47  /// Creates a new configuration adapter
48  pub fn new() -> Self {
49    Self
50  }
51}
52
53impl ConfigurationAdapter for YamlLegacyConfigurationAdapter {
54  fn load_configuration(&self, path: &Path) -> Result<Vec<ServerConfiguration>, Box<dyn Error + Send + Sync>> {
55    // Read and parse the configuration file contents
56    let kdl_document: KdlDocument = match convert_yaml_to_kdl(path.to_path_buf()) {
57      Ok(document) => document,
58      Err(err) => Err(anyhow::anyhow!(
59        "Failed to read and parse the server configuration file: {}",
60        err
61      ))?,
62    };
63
64    // Loaded configuration vector
65    let mut configurations = Vec::new();
66
67    // Iterate over KDL nodes
68    for kdl_node in kdl_document {
69      let global_name = kdl_node.name().value();
70      let children = kdl_node.children();
71      if let Some(children) = children {
72        for global_name in global_name.split(",") {
73          let (hostname, ip, port, is_host) = if global_name == "globals" {
74            (None, None, None, false)
75          } else if let Ok(socket_addr) = global_name.parse::<SocketAddr>() {
76            (None, Some(socket_addr.ip()), Some(socket_addr.port()), true)
77          } else if let Some((address, port_str)) = global_name.rsplit_once(':') {
78            if let Ok(port) = port_str.parse::<u16>() {
79              if let Ok(ip_address) = address
80                .strip_prefix('[')
81                .and_then(|s| s.strip_suffix(']'))
82                .unwrap_or(address)
83                .parse::<IpAddr>()
84              {
85                (None, Some(ip_address), Some(port), true)
86              } else if address == "*" || address.is_empty() {
87                (None, None, Some(port), true)
88              } else {
89                (Some(address.to_string()), None, Some(port), true)
90              }
91            } else if port_str == "*" {
92              if let Ok(ip_address) = address
93                .strip_prefix('[')
94                .and_then(|s| s.strip_suffix(']'))
95                .unwrap_or(address)
96                .parse::<IpAddr>()
97              {
98                (None, Some(ip_address), None, true)
99              } else if address == "*" || address.is_empty() {
100                (None, None, None, true)
101              } else {
102                (Some(address.to_string()), None, None, true)
103              }
104            } else {
105              Err(anyhow::anyhow!("Invalid host specifier"))?
106            }
107          } else if let Ok(ip_address) = global_name
108            .strip_prefix('[')
109            .and_then(|s| s.strip_suffix(']'))
110            .unwrap_or(global_name)
111            .parse::<IpAddr>()
112          {
113            (None, Some(ip_address), None, true)
114          } else if global_name == "*" || global_name.is_empty() {
115            (None, None, None, true)
116          } else {
117            (Some(global_name.to_string()), None, None, true)
118          };
119
120          let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
121          for kdl_node in children.nodes() {
122            let kdl_node_name = kdl_node.name().value();
123            let children = kdl_node.children();
124            if kdl_node_name == "location" {
125              let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
126              if let Some(children) = children {
127                if let Some(location) = kdl_node.entry(0) {
128                  if let Some(location_str) = location.value().as_string() {
129                    for kdl_node in children.nodes() {
130                      let kdl_node_name = kdl_node.name().value();
131                      let children = kdl_node.children();
132                      if kdl_node_name == "error_config" {
133                        let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
134                        if let Some(children) = children {
135                          if let Some(error_status_code) = kdl_node.entry(0) {
136                            if let Some(error_status_code) = error_status_code.value().as_integer() {
137                              for kdl_node in children.nodes() {
138                                let kdl_node_name = kdl_node.name().value();
139                                let value = kdl_node_to_configuration_entry(kdl_node);
140                                if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
141                                  entries.inner.push(value);
142                                } else {
143                                  configuration_entries.insert(
144                                    kdl_node_name.to_string(),
145                                    ServerConfigurationEntries { inner: vec![value] },
146                                  );
147                                }
148                              }
149                              configurations.push(ServerConfiguration {
150                                entries: configuration_entries,
151                                filters: ServerConfigurationFilters {
152                                  is_host,
153                                  hostname: hostname.clone(),
154                                  ip,
155                                  port,
156                                  condition: Some(Conditions {
157                                    location_prefix: location_str.to_string(),
158                                    conditionals: vec![],
159                                  }),
160                                  error_handler_status: Some(ErrorHandlerStatus::Status(error_status_code as u16)),
161                                },
162                                modules: vec![],
163                                observability: ObservabilityBackendChannels::new(),
164                              });
165                            } else {
166                              Err(anyhow::anyhow!("Invalid error handler status code"))?
167                            }
168                          } else {
169                            for kdl_node in children.nodes() {
170                              let kdl_node_name = kdl_node.name().value();
171                              let value = kdl_node_to_configuration_entry(kdl_node);
172                              if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
173                                entries.inner.push(value);
174                              } else {
175                                configuration_entries.insert(
176                                  kdl_node_name.to_string(),
177                                  ServerConfigurationEntries { inner: vec![value] },
178                                );
179                              }
180                            }
181                            configurations.push(ServerConfiguration {
182                              entries: configuration_entries,
183                              filters: ServerConfigurationFilters {
184                                is_host,
185                                hostname: hostname.clone(),
186                                ip,
187                                port,
188                                condition: Some(Conditions {
189                                  location_prefix: location_str.to_string(),
190                                  conditionals: vec![],
191                                }),
192                                error_handler_status: Some(ErrorHandlerStatus::Any),
193                              },
194                              modules: vec![],
195                              observability: ObservabilityBackendChannels::new(),
196                            });
197                          }
198                        } else {
199                          Err(anyhow::anyhow!(
200                            "Error handler blocks should have children, but they don't"
201                          ))?
202                        }
203                      } else {
204                        let value = kdl_node_to_configuration_entry(kdl_node);
205                        if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
206                          entries.inner.push(value);
207                        } else {
208                          configuration_entries.insert(
209                            kdl_node_name.to_string(),
210                            ServerConfigurationEntries { inner: vec![value] },
211                          );
212                        }
213                      }
214                    }
215                    if kdl_node
216                      .entry("remove_base")
217                      .and_then(|e| e.value().as_bool())
218                      .unwrap_or(false)
219                    {
220                      configuration_entries.insert(
221                        "UNDOCUMENTED_REMOVE_PATH_PREFIX".to_string(),
222                        ServerConfigurationEntries {
223                          inner: vec![ServerConfigurationEntry {
224                            values: vec![ServerConfigurationValue::String(location_str.to_string())],
225                            props: HashMap::new(),
226                          }],
227                        },
228                      );
229                    }
230                    configurations.push(ServerConfiguration {
231                      entries: configuration_entries,
232                      filters: ServerConfigurationFilters {
233                        is_host,
234                        hostname: hostname.clone(),
235                        ip,
236                        port,
237                        condition: Some(Conditions {
238                          location_prefix: location_str.to_string(),
239                          conditionals: vec![],
240                        }),
241                        error_handler_status: None,
242                      },
243                      modules: vec![],
244                      observability: ObservabilityBackendChannels::new(),
245                    });
246                  } else {
247                    Err(anyhow::anyhow!("Invalid location path"))?
248                  }
249                } else {
250                  Err(anyhow::anyhow!("Invalid location"))?
251                }
252              } else {
253                Err(anyhow::anyhow!("Locations should have children, but they don't"))?
254              }
255            } else if kdl_node_name == "error_config" {
256              let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
257              if let Some(children) = children {
258                if let Some(error_status_code) = kdl_node.entry(0) {
259                  if let Some(error_status_code) = error_status_code.value().as_integer() {
260                    for kdl_node in children.nodes() {
261                      let kdl_node_name = kdl_node.name().value();
262                      let value = kdl_node_to_configuration_entry(kdl_node);
263                      if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
264                        entries.inner.push(value);
265                      } else {
266                        configuration_entries.insert(
267                          kdl_node_name.to_string(),
268                          ServerConfigurationEntries { inner: vec![value] },
269                        );
270                      }
271                    }
272                    configurations.push(ServerConfiguration {
273                      entries: configuration_entries,
274                      filters: ServerConfigurationFilters {
275                        is_host,
276                        hostname: hostname.clone(),
277                        ip,
278                        port,
279                        condition: None,
280                        error_handler_status: Some(ErrorHandlerStatus::Status(error_status_code as u16)),
281                      },
282                      modules: vec![],
283                      observability: ObservabilityBackendChannels::new(),
284                    });
285                  } else {
286                    Err(anyhow::anyhow!("Invalid error handler status code"))?
287                  }
288                } else {
289                  for kdl_node in children.nodes() {
290                    let kdl_node_name = kdl_node.name().value();
291                    let value = kdl_node_to_configuration_entry(kdl_node);
292                    if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
293                      entries.inner.push(value);
294                    } else {
295                      configuration_entries.insert(
296                        kdl_node_name.to_string(),
297                        ServerConfigurationEntries { inner: vec![value] },
298                      );
299                    }
300                  }
301                  configurations.push(ServerConfiguration {
302                    entries: configuration_entries,
303                    filters: ServerConfigurationFilters {
304                      is_host,
305                      hostname: hostname.clone(),
306                      ip,
307                      port,
308                      condition: None,
309                      error_handler_status: Some(ErrorHandlerStatus::Any),
310                    },
311                    modules: vec![],
312                    observability: ObservabilityBackendChannels::new(),
313                  });
314                }
315              } else {
316                Err(anyhow::anyhow!(
317                  "Error handler blocks should have children, but they don't"
318                ))?
319              }
320            } else {
321              let value = kdl_node_to_configuration_entry(kdl_node);
322              if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
323                entries.inner.push(value);
324              } else {
325                configuration_entries.insert(
326                  kdl_node_name.to_string(),
327                  ServerConfigurationEntries { inner: vec![value] },
328                );
329              }
330            }
331          }
332          configurations.push(ServerConfiguration {
333            entries: configuration_entries,
334            filters: ServerConfigurationFilters {
335              is_host,
336              hostname,
337              ip,
338              port,
339              condition: None,
340              error_handler_status: None,
341            },
342            modules: vec![],
343            observability: ObservabilityBackendChannels::new(),
344          });
345        }
346      } else {
347        // "include" directives aren't generated by `ferron-yaml2kdl-core`
348        Err(anyhow::anyhow!("Invalid top-level directive"))?
349      }
350    }
351
352    Ok(configurations)
353  }
354}