ferron/config/adapters/
kdl.rs

1use std::{
2  collections::{HashMap, HashSet},
3  error::Error,
4  fs,
5  net::{IpAddr, SocketAddr},
6  path::{Path, PathBuf},
7  str::FromStr,
8};
9
10use ferron_common::observability::ObservabilityBackendChannels;
11use glob::glob;
12use kdl::{KdlDocument, KdlNode, KdlValue};
13
14use crate::config::{
15  parse_conditional_data, Conditional, ConditionalData, Conditions, ErrorHandlerStatus, ServerConfiguration,
16  ServerConfigurationEntries, ServerConfigurationEntry, ServerConfigurationFilters, ServerConfigurationValue,
17};
18
19use super::ConfigurationAdapter;
20
21fn kdl_node_to_configuration_entry(kdl_node: &KdlNode) -> ServerConfigurationEntry {
22  let mut values = Vec::new();
23  let mut props = HashMap::new();
24  for kdl_entry in kdl_node.iter() {
25    let value = match kdl_entry.value().to_owned() {
26      KdlValue::String(value) => ServerConfigurationValue::String(value),
27      KdlValue::Integer(value) => ServerConfigurationValue::Integer(value),
28      KdlValue::Float(value) => ServerConfigurationValue::Float(value),
29      KdlValue::Bool(value) => ServerConfigurationValue::Bool(value),
30      KdlValue::Null => ServerConfigurationValue::Null,
31    };
32    if let Some(prop_name) = kdl_entry.name() {
33      props.insert(prop_name.value().to_string(), value);
34    } else {
35      values.push(value);
36    }
37  }
38  if values.is_empty() {
39    // If KDL node doesn't have any arguments, add the "#true" KDL value
40    values.push(ServerConfigurationValue::Bool(true));
41  }
42  ServerConfigurationEntry { values, props }
43}
44
45fn load_configuration_inner(
46  path: PathBuf,
47  loaded_paths: &mut HashSet<PathBuf>,
48  snippets: &mut HashMap<String, KdlDocument>,
49) -> Result<Vec<ServerConfiguration>, Box<dyn Error + Send + Sync>> {
50  // Canonicalize the path
51  let canonical_pathbuf = fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
52
53  // Check if the path is duplicate. If it's not, add it to loaded paths.
54  if loaded_paths.contains(&canonical_pathbuf) {
55    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
56
57    Err(anyhow::anyhow!(
58      "Detected the server configuration file include loop while attempting to load \"{}\"",
59      canonical_path
60    ))?
61  } else {
62    loaded_paths.insert(canonical_pathbuf.clone());
63  }
64
65  // Read the configuration file
66  let file_contents = match fs::read_to_string(&path) {
67    Ok(file) => file,
68    Err(err) => {
69      let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
70
71      Err(anyhow::anyhow!(
72        "Failed to read from the server configuration file at \"{}\": {}",
73        canonical_path,
74        err
75      ))?
76    }
77  };
78
79  // Parse the configuration file contents
80  let kdl_document: KdlDocument = match file_contents.parse() {
81    Ok(document) => document,
82    Err(err) => {
83      let err: miette::Error = err.into();
84      Err(anyhow::anyhow!(
85        "Failed to parse the server configuration file: {:?}",
86        err
87      ))?
88    }
89  };
90
91  // Loaded configuration vector
92  let mut configurations = Vec::new();
93
94  // Loaded conditions
95  let mut loaded_conditions: HashMap<String, Vec<ConditionalData>> = HashMap::new();
96
97  // Iterate over KDL nodes
98  for kdl_node in kdl_document {
99    let global_name = kdl_node.name().value();
100    let children = kdl_node.children();
101    if global_name == "snippet" {
102      if let Some(snippet_name) = kdl_node.get(0).and_then(|v| v.as_string()) {
103        if let Some(children) = children {
104          snippets.insert(snippet_name.to_string(), children.to_owned());
105        } else {
106          Err(anyhow::anyhow!("Snippet \"{snippet_name}\" is missing children"))?
107        }
108      } else {
109        Err(anyhow::anyhow!("Invalid or missing snippet name"))?
110      }
111    } else if let Some(children) = children {
112      for global_name in global_name.split(",") {
113        let host_filter = if global_name == "globals" {
114          (None, None, None, false)
115        } else if let Ok(socket_addr) = global_name.parse::<SocketAddr>() {
116          (None, Some(socket_addr.ip()), Some(socket_addr.port()), true)
117        } else if let Some((address, port_str)) = global_name.rsplit_once(':') {
118          if let Ok(port) = port_str.parse::<u16>() {
119            if let Ok(ip_address) = address
120              .strip_prefix('[')
121              .and_then(|s| s.strip_suffix(']'))
122              .unwrap_or(address)
123              .parse::<IpAddr>()
124            {
125              (None, Some(ip_address), Some(port), true)
126            } else if address == "*" || address.is_empty() {
127              (None, None, Some(port), true)
128            } else {
129              (Some(address.to_string()), None, Some(port), true)
130            }
131          } else if port_str == "*" {
132            if let Ok(ip_address) = address
133              .strip_prefix('[')
134              .and_then(|s| s.strip_suffix(']'))
135              .unwrap_or(address)
136              .parse::<IpAddr>()
137            {
138              (None, Some(ip_address), None, true)
139            } else if address == "*" || address.is_empty() {
140              (None, None, None, true)
141            } else {
142              (Some(address.to_string()), None, None, true)
143            }
144          } else {
145            let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
146
147            Err(anyhow::anyhow!("Invalid host specifier at \"{}\"", canonical_path))?
148          }
149        } else if let Ok(ip_address) = global_name
150          .strip_prefix('[')
151          .and_then(|s| s.strip_suffix(']'))
152          .unwrap_or(global_name)
153          .parse::<IpAddr>()
154        {
155          (None, Some(ip_address), None, true)
156        } else if global_name == "*" || global_name.is_empty() {
157          (None, None, None, true)
158        } else {
159          (Some(global_name.to_string()), None, None, true)
160        };
161
162        let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
163        for kdl_node in children.nodes() {
164          #[allow(clippy::too_many_arguments)]
165          fn kdl_iterate_fn(
166            canonical_pathbuf: &PathBuf,
167            host_filter: &(Option<String>, Option<IpAddr>, Option<u16>, bool),
168            configurations: &mut Vec<ServerConfiguration>,
169            configuration_entries: &mut HashMap<String, ServerConfigurationEntries>,
170            kdl_node: &KdlNode,
171            conditions: &mut Option<&mut Conditions>,
172            is_error_config: bool,
173            loaded_conditions: &mut HashMap<String, Vec<ConditionalData>>,
174            snippets: &HashMap<String, KdlDocument>,
175          ) -> Result<(), Box<dyn Error + Send + Sync>> {
176            let (hostname, ip, port, is_host) = host_filter;
177            let kdl_node_name = kdl_node.name().value();
178            let children = kdl_node.children();
179            if kdl_node_name == "use" {
180              if let Some(snippet_name) = kdl_node.entry(0).and_then(|e| e.value().as_string()) {
181                if let Some(snippet) = snippets.get(snippet_name) {
182                  for kdl_node in snippet.nodes() {
183                    kdl_iterate_fn(
184                      canonical_pathbuf,
185                      host_filter,
186                      configurations,
187                      configuration_entries,
188                      kdl_node,
189                      conditions,
190                      is_error_config,
191                      loaded_conditions,
192                      snippets,
193                    )?;
194                  }
195                } else {
196                  Err(anyhow::anyhow!(
197                    "Snippet not defined: {snippet_name}. You might need to define it before using it"
198                  ))?;
199                }
200              } else {
201                Err(anyhow::anyhow!("Invalid `use` statement"))?;
202              }
203            } else if kdl_node_name == "location" {
204              if is_error_config {
205                Err(anyhow::anyhow!("Locations in error configurations aren't allowed"))?;
206              } else if conditions.is_some() {
207                Err(anyhow::anyhow!(
208                  "Nested locations and locations in conditions aren't allowed"
209                ))?;
210              }
211              let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
212              if let Some(children) = children {
213                if let Some(location) = kdl_node.entry(0) {
214                  if let Some(location_str) = location.value().as_string() {
215                    let mut conditions = Conditions {
216                      location_prefix: location_str.to_string(),
217                      conditionals: vec![],
218                    };
219                    let mut loaded_conditions = loaded_conditions.clone();
220                    for kdl_node in children.nodes() {
221                      kdl_iterate_fn(
222                        canonical_pathbuf,
223                        host_filter,
224                        configurations,
225                        &mut configuration_entries,
226                        kdl_node,
227                        &mut Some(&mut conditions),
228                        is_error_config,
229                        &mut loaded_conditions,
230                        snippets,
231                      )?;
232                    }
233                    if kdl_node
234                      .entry("remove_base")
235                      .and_then(|e| e.value().as_bool())
236                      .unwrap_or(false)
237                    {
238                      configuration_entries.insert(
239                        "UNDOCUMENTED_REMOVE_PATH_PREFIX".to_string(),
240                        ServerConfigurationEntries {
241                          inner: vec![ServerConfigurationEntry {
242                            values: vec![ServerConfigurationValue::String(location_str.to_string())],
243                            props: HashMap::new(),
244                          }],
245                        },
246                      );
247                    }
248                    configurations.push(ServerConfiguration {
249                      entries: configuration_entries,
250                      filters: ServerConfigurationFilters {
251                        is_host: *is_host,
252                        hostname: hostname.clone(),
253                        ip: *ip,
254                        port: *port,
255                        condition: Some(conditions),
256                        error_handler_status: None,
257                      },
258                      modules: vec![],
259                      observability: ObservabilityBackendChannels::new(),
260                    });
261                  } else {
262                    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
263
264                    Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
265                  }
266                } else {
267                  let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
268
269                  Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
270                }
271              } else {
272                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
273
274                Err(anyhow::anyhow!(
275                  "Locations should have children, but they don't at \"{}\"",
276                  canonical_path
277                ))?
278              }
279            } else if kdl_node_name == "condition" {
280              if is_error_config {
281                Err(anyhow::anyhow!("Conditions in error configurations aren't allowed"))?;
282              }
283              if let Some(children) = children {
284                if let Some(condition_name) = kdl_node.entry(0) {
285                  if let Some(condition_name_str) = condition_name.value().as_string() {
286                    let mut conditions_data = Vec::new();
287
288                    let mut nodes_stack = Vec::new();
289                    nodes_stack.push(children.nodes().iter());
290
291                    while let Some(kdl_node) = {
292                      let mut last_iterator_item = None;
293                      while last_iterator_item.is_none() && !nodes_stack.is_empty() {
294                        last_iterator_item = nodes_stack.last_mut().and_then(|i| i.next());
295                        if last_iterator_item.is_none() {
296                          nodes_stack.pop();
297                        }
298                      }
299                      last_iterator_item
300                    } {
301                      let name = kdl_node.name().value();
302                      if name == "use" {
303                        if let Some(snippet_name) = kdl_node.get(0).and_then(|v| v.as_string()) {
304                          if let Some(snippet) = snippets.get(snippet_name) {
305                            nodes_stack.push(snippet.nodes().iter());
306                            continue;
307                          } else {
308                            Err(anyhow::anyhow!(
309                              "Snippet not defined: {snippet_name}. You might need to define it before using it"
310                            ))?;
311                          }
312                        } else {
313                          Err(anyhow::anyhow!("Invalid `use` statement"))?;
314                        }
315                      }
316                      let value = kdl_node_to_configuration_entry(kdl_node);
317                      conditions_data.push(match parse_conditional_data(name, value) {
318                        Ok(d) => d,
319                        Err(err) => Err(anyhow::anyhow!(
320                          "Invalid or unsupported subcondition at \"{condition_name_str}\" condition: {err}"
321                        ))?,
322                      });
323                    }
324
325                    loaded_conditions.insert(condition_name_str.to_string(), conditions_data);
326                  } else {
327                    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
328
329                    Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
330                  }
331                } else {
332                  let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
333
334                  Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
335                }
336              } else {
337                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
338
339                Err(anyhow::anyhow!(
340                  "Locations should have children, but they don't at \"{}\"",
341                  canonical_path
342                ))?
343              }
344            } else if kdl_node_name == "if" {
345              if is_error_config {
346                Err(anyhow::anyhow!("Conditions in error configurations aren't allowed"))?;
347              }
348              let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
349              if let Some(children) = children {
350                if let Some(condition_name) = kdl_node.entry(0) {
351                  if let Some(condition_name_str) = condition_name.value().as_string() {
352                    let mut new_conditions = if let Some(conditions) = conditions {
353                      conditions.clone()
354                    } else {
355                      Conditions {
356                        location_prefix: "/".to_string(),
357                        conditionals: vec![],
358                      }
359                    };
360
361                    if let Some(conditionals) = loaded_conditions.get(condition_name_str) {
362                      new_conditions.conditionals.push(Conditional::If(conditionals.clone()));
363                    } else {
364                      Err(anyhow::anyhow!(
365                        "Condition not defined: {condition_name_str}. You might need to define it before using it"
366                      ))?;
367                    }
368
369                    let mut loaded_conditions = loaded_conditions.clone();
370                    for kdl_node in children.nodes() {
371                      kdl_iterate_fn(
372                        canonical_pathbuf,
373                        host_filter,
374                        configurations,
375                        &mut configuration_entries,
376                        kdl_node,
377                        &mut Some(&mut new_conditions),
378                        is_error_config,
379                        &mut loaded_conditions,
380                        snippets,
381                      )?;
382                    }
383
384                    configurations.push(ServerConfiguration {
385                      entries: configuration_entries,
386                      filters: ServerConfigurationFilters {
387                        is_host: *is_host,
388                        hostname: hostname.clone(),
389                        ip: *ip,
390                        port: *port,
391                        condition: Some(new_conditions.to_owned()),
392                        error_handler_status: None,
393                      },
394                      modules: vec![],
395                      observability: ObservabilityBackendChannels::new(),
396                    });
397                  } else {
398                    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
399
400                    Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
401                  }
402                } else {
403                  let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
404
405                  Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
406                }
407              } else {
408                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
409
410                Err(anyhow::anyhow!(
411                  "Locations should have children, but they don't at \"{}\"",
412                  canonical_path
413                ))?
414              }
415            } else if kdl_node_name == "if_not" {
416              if is_error_config {
417                Err(anyhow::anyhow!("Conditions in error configurations aren't allowed"))?;
418              }
419              let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
420              if let Some(children) = children {
421                if let Some(condition_name) = kdl_node.entry(0) {
422                  if let Some(condition_name_str) = condition_name.value().as_string() {
423                    let mut new_conditions = if let Some(conditions) = conditions {
424                      conditions.clone()
425                    } else {
426                      Conditions {
427                        location_prefix: "/".to_string(),
428                        conditionals: vec![],
429                      }
430                    };
431
432                    if let Some(conditionals) = loaded_conditions.get(condition_name_str) {
433                      new_conditions
434                        .conditionals
435                        .push(Conditional::IfNot(conditionals.clone()));
436                    } else {
437                      Err(anyhow::anyhow!(
438                        "Condition not defined: {condition_name_str}. You might need to define it before using it"
439                      ))?;
440                    }
441
442                    let mut loaded_conditions = loaded_conditions.clone();
443                    for kdl_node in children.nodes() {
444                      kdl_iterate_fn(
445                        canonical_pathbuf,
446                        host_filter,
447                        configurations,
448                        &mut configuration_entries,
449                        kdl_node,
450                        &mut Some(&mut new_conditions),
451                        is_error_config,
452                        &mut loaded_conditions,
453                        snippets,
454                      )?;
455                    }
456
457                    configurations.push(ServerConfiguration {
458                      entries: configuration_entries,
459                      filters: ServerConfigurationFilters {
460                        is_host: *is_host,
461                        hostname: hostname.clone(),
462                        ip: *ip,
463                        port: *port,
464                        condition: Some(new_conditions.to_owned()),
465                        error_handler_status: None,
466                      },
467                      modules: vec![],
468                      observability: ObservabilityBackendChannels::new(),
469                    });
470                  } else {
471                    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
472
473                    Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
474                  }
475                } else {
476                  let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
477
478                  Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
479                }
480              } else {
481                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
482
483                Err(anyhow::anyhow!(
484                  "Locations should have children, but they don't at \"{}\"",
485                  canonical_path
486                ))?
487              }
488            } else if kdl_node_name == "error_config" {
489              if is_error_config {
490                Err(anyhow::anyhow!("Nested error configurations aren't allowed"))?;
491              }
492              let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
493              if let Some(children) = children {
494                if let Some(error_status_code) = kdl_node.entry(0) {
495                  if let Some(error_status_code) = error_status_code.value().as_integer() {
496                    let mut loaded_conditions = loaded_conditions.clone();
497                    for kdl_node in children.nodes() {
498                      kdl_iterate_fn(
499                        canonical_pathbuf,
500                        host_filter,
501                        configurations,
502                        &mut configuration_entries,
503                        kdl_node,
504                        conditions,
505                        true,
506                        &mut loaded_conditions,
507                        snippets,
508                      )?;
509                    }
510                    configurations.push(ServerConfiguration {
511                      entries: configuration_entries,
512                      filters: ServerConfigurationFilters {
513                        is_host: *is_host,
514                        hostname: hostname.clone(),
515                        ip: *ip,
516                        port: *port,
517                        condition: None,
518                        error_handler_status: Some(ErrorHandlerStatus::Status(error_status_code as u16)),
519                      },
520                      modules: vec![],
521                      observability: ObservabilityBackendChannels::new(),
522                    });
523                  } else {
524                    let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
525
526                    Err(anyhow::anyhow!(
527                      "Invalid error handler status code at \"{}\"",
528                      canonical_path
529                    ))?
530                  }
531                } else {
532                  for kdl_node in children.nodes() {
533                    let kdl_node_name = kdl_node.name().value();
534                    let value = kdl_node_to_configuration_entry(kdl_node);
535                    if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
536                      entries.inner.push(value);
537                    } else {
538                      configuration_entries.insert(
539                        kdl_node_name.to_string(),
540                        ServerConfigurationEntries { inner: vec![value] },
541                      );
542                    }
543                  }
544                  configurations.push(ServerConfiguration {
545                    entries: configuration_entries,
546                    filters: ServerConfigurationFilters {
547                      is_host: *is_host,
548                      hostname: hostname.clone(),
549                      ip: *ip,
550                      port: *port,
551                      condition: None,
552                      error_handler_status: Some(ErrorHandlerStatus::Any),
553                    },
554                    modules: vec![],
555                    observability: ObservabilityBackendChannels::new(),
556                  });
557                }
558              } else {
559                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
560
561                Err(anyhow::anyhow!(
562                  "Error handler blocks should have children, but they don't at \"{}\"",
563                  canonical_path
564                ))?
565              }
566            } else {
567              let value = kdl_node_to_configuration_entry(kdl_node);
568              if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
569                entries.inner.push(value);
570              } else {
571                configuration_entries.insert(
572                  kdl_node_name.to_string(),
573                  ServerConfigurationEntries { inner: vec![value] },
574                );
575              }
576            }
577            Ok(())
578          }
579          kdl_iterate_fn(
580            &canonical_pathbuf,
581            &host_filter,
582            &mut configurations,
583            &mut configuration_entries,
584            kdl_node,
585            &mut None,
586            false,
587            &mut loaded_conditions,
588            snippets,
589          )?;
590        }
591        let (hostname, ip, port, is_host) = host_filter;
592        configurations.push(ServerConfiguration {
593          entries: configuration_entries,
594          filters: ServerConfigurationFilters {
595            is_host,
596            hostname,
597            ip,
598            port,
599            condition: None,
600            error_handler_status: None,
601          },
602          modules: vec![],
603          observability: ObservabilityBackendChannels::new(),
604        });
605      }
606    } else if global_name == "include" {
607      // Get the list of included files and include the configurations
608      let mut include_files = Vec::new();
609      for include_one in kdl_node.entries() {
610        if include_one.name().is_some() {
611          continue;
612        }
613        if let Some(include_glob) = include_one.value().as_string() {
614          let include_glob_pathbuf = match PathBuf::from_str(include_glob) {
615            Ok(pathbuf) => pathbuf,
616            Err(err) => {
617              let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
618
619              Err(anyhow::anyhow!(
620                "Failed to determine includes for the server configuration file at \"{}\": {}",
621                canonical_path,
622                err
623              ))?
624            }
625          };
626          let include_glob_pathbuf_canonicalized = if include_glob_pathbuf.is_absolute() {
627            include_glob_pathbuf
628          } else {
629            let mut canonical_dirname = canonical_pathbuf.clone();
630            canonical_dirname.pop();
631            canonical_dirname.join(include_glob_pathbuf)
632          };
633          let files_globbed = match glob(&include_glob_pathbuf_canonicalized.to_string_lossy()) {
634            Ok(files_globbed) => files_globbed,
635            Err(err) => {
636              let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
637
638              Err(anyhow::anyhow!(
639                "Failed to determine includes for the server configuration file at \"{}\": {}",
640                canonical_path,
641                err
642              ))?
643            }
644          };
645
646          for file_globbed_result in files_globbed {
647            let file_globbed = match file_globbed_result {
648              Ok(file_globbed) => file_globbed,
649              Err(err) => {
650                let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
651
652                Err(anyhow::anyhow!(
653                  "Failed to determine includes for the server configuration file at \"{}\": {}",
654                  canonical_path,
655                  err
656                ))?
657              }
658            };
659            include_files.push(fs::canonicalize(&file_globbed).unwrap_or_else(|_| file_globbed.clone()));
660          }
661        }
662      }
663
664      for included_file in include_files {
665        configurations.extend(load_configuration_inner(included_file, loaded_paths, snippets)?);
666      }
667    } else {
668      let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
669
670      Err(anyhow::anyhow!("Invalid top-level directive at \"{}\"", canonical_path))?
671    }
672  }
673
674  Ok(configurations)
675}
676
677/// A KDL configuration adapter
678pub struct KdlConfigurationAdapter;
679
680impl KdlConfigurationAdapter {
681  /// Creates a new configuration adapter
682  pub fn new() -> Self {
683    Self
684  }
685}
686
687impl ConfigurationAdapter for KdlConfigurationAdapter {
688  fn load_configuration(&self, path: &Path) -> Result<Vec<ServerConfiguration>, Box<dyn Error + Send + Sync>> {
689    load_configuration_inner(path.to_path_buf(), &mut HashSet::new(), &mut HashMap::new())
690  }
691}