wick_config/config/
configuration_tree.rs

1#![allow(missing_docs)]
2
3use wick_asset_reference::FetchOptions;
4use wick_packet::RuntimeConfig;
5
6use super::{ImportDefinition, UninitializedConfiguration};
7use crate::config::{Binding, ComponentDefinition};
8use crate::error::ManifestError;
9use crate::{Imports, WickConfiguration};
10
11#[derive(Debug)]
12
13pub enum ConfigOrDefinition<T> {
14  Config(ConfigurationTreeNode<T>),
15  Definition { id: String, element: ComponentDefinition },
16}
17
18impl<T> ConfigOrDefinition<T> {
19  /// Returns the held configuration if it is a [ConfigurationTreeNode].
20  #[must_use]
21  #[allow(clippy::missing_const_for_fn)]
22  pub fn as_config(self) -> Option<T> {
23    match self {
24      ConfigOrDefinition::Config(c) => Some(c.element),
25      ConfigOrDefinition::Definition { .. } => None,
26    }
27  }
28  /// Returns the held configuration if it is a [ComponentDefinition].
29  #[must_use]
30  #[allow(clippy::missing_const_for_fn)]
31  pub fn as_component_definition(self) -> Option<(String, ComponentDefinition)> {
32    match self {
33      ConfigOrDefinition::Config(_) => None,
34      ConfigOrDefinition::Definition { id, element } => Some((id, element)),
35    }
36  }
37}
38
39#[derive(Debug)]
40#[non_exhaustive]
41pub struct ConfigurationTreeNode<T> {
42  pub name: String,
43  pub element: T,
44  pub children: Vec<ConfigOrDefinition<T>>,
45}
46
47impl<T> ConfigurationTreeNode<T>
48where
49  T: Imports + Send + Sync,
50{
51  #[must_use]
52  pub const fn new(name: String, element: T, children: Vec<ConfigOrDefinition<T>>) -> Self {
53    Self {
54      name,
55      element,
56      children,
57    }
58  }
59
60  /// Flattens a configuration tree into a list of its namespaces elements.
61  #[must_use]
62  pub fn flatten(self) -> Vec<ConfigOrDefinition<T>> {
63    let id = self.name.clone();
64    flatten(self, &id)
65  }
66}
67
68#[async_recursion::async_recursion]
69pub async fn fetch_children<T, H, U>(
70  config: &T,
71  options: FetchOptions,
72  processor: &H,
73) -> Result<Vec<ConfigOrDefinition<U>>, ManifestError>
74where
75  T: Imports + Send + Sync,
76  H: Fn(Option<RuntimeConfig>, UninitializedConfiguration) -> Result<U, ManifestError> + Send + Sync,
77  U: Imports + Send + Sync,
78{
79  let imports = config.imports().to_vec();
80
81  let mut children = fetch_imports(imports, options.clone(), processor).await?;
82  for child in &mut children {
83    match child {
84      ConfigOrDefinition::Config(ref mut c) => {
85        let children = fetch_children(&c.element, options.clone(), processor).await?;
86        c.children = children;
87      }
88      ConfigOrDefinition::Definition { .. } => {}
89    }
90  }
91  Ok(children)
92}
93
94#[must_use]
95pub fn flatten<T>(node: ConfigurationTreeNode<T>, prefix: &str) -> Vec<ConfigOrDefinition<T>> {
96  let mut flattened = Vec::new();
97  let children = node.children;
98  let new_node = ConfigurationTreeNode {
99    name: prefix.to_owned(),
100    element: node.element,
101    children: Vec::new(),
102  };
103  flattened.push(ConfigOrDefinition::Config(new_node));
104  for child in children {
105    match child {
106      ConfigOrDefinition::Config(c) => {
107        let id = format!("{}::{}", prefix, c.name);
108        let new_node = ConfigurationTreeNode {
109          name: id.clone(),
110          element: c.element,
111          children: c.children,
112        };
113
114        flattened.extend(flatten(new_node, &id));
115      }
116      ConfigOrDefinition::Definition { .. } => flattened.push(child),
117    }
118  }
119  flattened
120}
121
122async fn fetch_imports<H, T>(
123  imports: Vec<Binding<ImportDefinition>>,
124  options: FetchOptions,
125  processor: &H,
126) -> Result<Vec<ConfigOrDefinition<T>>, ManifestError>
127where
128  T: Imports + Send + Sync,
129  H: Fn(Option<RuntimeConfig>, UninitializedConfiguration) -> Result<T, ManifestError> + Send + Sync,
130{
131  let mut children: Vec<ConfigOrDefinition<T>> = Vec::new();
132  for import in imports {
133    let id = import.id().to_owned();
134
135    match import.kind {
136      super::ImportDefinition::Component(c) => match c {
137        super::ComponentDefinition::Manifest(c) => {
138          let config = WickConfiguration::fetch(c.reference.clone(), options.clone()).await?;
139          let config = processor(c.config().and_then(|c| c.value.clone()), config)?;
140
141          children.push(ConfigOrDefinition::Config(ConfigurationTreeNode::new(
142            id,
143            config,
144            Vec::new(),
145          )));
146        }
147        component => {
148          children.push(ConfigOrDefinition::Definition {
149            id,
150            element: component.clone(),
151          });
152        }
153      },
154      super::ImportDefinition::Types(_) => {}
155    }
156  }
157  Ok(children)
158}
159
160#[cfg(test)]
161mod test {
162  use anyhow::Result;
163
164  use super::*;
165  use crate::config::{components, AppConfigurationBuilder, Binding, ComponentDefinition, ImportDefinition};
166
167  #[test_logger::test(tokio::test)]
168  async fn test_tree_walker() -> Result<()> {
169    let mut config = AppConfigurationBuilder::default();
170
171    config
172      .name("app")
173      .options(FetchOptions::default())
174      .import(vec![Binding::new(
175        "SUB_COMPONENT",
176        ImportDefinition::Component(ComponentDefinition::Manifest(
177          components::ManifestComponentBuilder::default()
178            .reference("tests/manifests/v1/component-resources.yaml")
179            .build()?,
180        )),
181      )]);
182    let config = config.build()?;
183    let config = UninitializedConfiguration::new(WickConfiguration::App(config));
184    let children = fetch_children(&config, Default::default(), &|_, c| Ok(c)).await?;
185    assert_eq!(children.len(), 1);
186    Ok(())
187  }
188}