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