wick_config/config/common/
component_definition.rs

1#![allow(missing_docs, deprecated)]
2use std::collections::HashMap;
3use std::path::Path;
4
5// delete when we move away from the `property` crate.
6use serde::de::{IgnoredAny, SeqAccess, Visitor};
7use serde::Deserializer;
8use wick_interface_types::OperationSignatures;
9use wick_packet::{Entity, RuntimeConfig};
10
11use super::template_config::Renderable;
12use super::Binding;
13use crate::config::{self, ExecutionSettings, ImportDefinition, LiquidJsonConfig};
14use crate::error::ManifestError;
15
16/// A reference to an operation.
17#[derive(
18  Debug,
19  Clone,
20  PartialEq,
21  derive_asset_container::AssetManager,
22  property::Property,
23  serde::Serialize,
24  derive_builder::Builder,
25)]
26#[builder(setter(into))]
27#[property(get(public), set(public), mut(public, suffix = "_mut"))]
28#[asset(asset(config::AssetReference))]
29
30pub struct ComponentOperationExpression {
31  /// The operation ID.
32  #[asset(skip)]
33  pub(crate) name: String,
34  /// The component referenced by identifier or anonymously.
35  pub(crate) component: ComponentDefinition,
36  /// Configuration to associate with this operation.
37  #[asset(skip)]
38  #[builder(default)]
39  #[serde(skip_serializing_if = "Option::is_none")]
40  pub(crate) config: Option<LiquidJsonConfig>,
41  /// Per-operation settings that override global execution settings.
42  #[asset(skip)]
43  #[builder(default)]
44  #[serde(skip_serializing_if = "Option::is_none")]
45  pub(crate) settings: Option<ExecutionSettings>,
46}
47
48impl ComponentOperationExpression {
49  /// Create a new [ComponentOperationExpression] with specified operation and component.
50  pub fn new_default<T: Into<String>>(operation: T, component: ComponentDefinition) -> Self {
51    Self {
52      name: operation.into(),
53      component,
54      config: Default::default(),
55      settings: Default::default(),
56    }
57  }
58
59  /// Create a new [ComponentOperationExpression] with specified operation and component.
60  pub fn new<T: Into<String>>(
61    operation: T,
62    component: ComponentDefinition,
63    config: Option<LiquidJsonConfig>,
64    settings: Option<ExecutionSettings>,
65  ) -> Self {
66    Self {
67      name: operation.into(),
68      component,
69      config,
70      settings,
71    }
72  }
73
74  pub fn maybe_import(&mut self, import_name: &str, bindings: &mut Vec<Binding<ImportDefinition>>) {
75    if self.component.is_reference() {
76      return;
77    }
78
79    tracing::Span::current().in_scope(|| {
80      tracing::debug!("importing inline component as {}", import_name);
81      let def = std::mem::replace(
82        &mut self.component,
83        ComponentDefinition::Reference(config::components::ComponentReference::new(import_name)),
84      );
85      bindings.push(Binding::new(import_name, config::ImportDefinition::Component(def)));
86    });
87  }
88
89  /// Get the operation as an [Entity] if the component definition is a referencable instance.
90  #[must_use]
91  pub fn as_entity(&self) -> Option<Entity> {
92    if let ComponentDefinition::Reference(r) = &self.component {
93      Some(Entity::operation(&r.id, &self.name))
94    } else {
95      None
96    }
97  }
98
99  pub fn component_id(&self) -> Result<&str, ManifestError> {
100    match &self.component {
101      ComponentDefinition::Reference(r) => Ok(r.id()),
102      _ => Err(ManifestError::InvalidReference),
103    }
104  }
105}
106
107impl Renderable for ComponentOperationExpression {
108  fn render_config(
109    &mut self,
110    source: Option<&Path>,
111    root_config: Option<&RuntimeConfig>,
112    env: Option<&HashMap<String, String>>,
113  ) -> Result<(), ManifestError> {
114    if let Some(config) = self.config.as_mut() {
115      config.set_value(Some(config.render(source, root_config, None, env, None)?));
116    }
117    Ok(())
118  }
119}
120
121impl std::str::FromStr for ComponentOperationExpression {
122  type Err = crate::Error;
123
124  fn from_str(s: &str) -> Result<Self, Self::Err> {
125    let mut parts = s.split("::");
126
127    let operation = parts
128      .next()
129      .ok_or_else(|| crate::Error::InvalidOperationExpression(s.to_owned()))?
130      .to_owned();
131    let component = parts
132      .next()
133      .ok_or_else(|| crate::Error::InvalidOperationExpression(s.to_owned()))?
134      .to_owned();
135
136    Ok(Self {
137      name: operation,
138      component: ComponentDefinition::Reference(config::components::ComponentReference { id: component }),
139      config: Default::default(),
140      settings: Default::default(),
141    })
142  }
143}
144
145#[derive(Debug, Clone, PartialEq, derive_asset_container::AssetManager, serde::Serialize)]
146#[asset(asset(config::AssetReference))]
147/// A definition of a Wick Collection with its namespace, how to retrieve or access it and its configuration.
148#[must_use]
149#[serde(rename_all = "kebab-case")]
150pub enum HighLevelComponent {
151  /// A SQL Component.
152  #[asset(skip)]
153  Sql(config::components::SqlComponentConfig),
154  #[asset(skip)]
155  /// An HTTP Client Component.
156  HttpClient(config::components::HttpClientComponentConfig),
157}
158
159impl OperationSignatures for HighLevelComponent {
160  fn operation_signatures(&self) -> Vec<wick_interface_types::OperationSignature> {
161    match self {
162      HighLevelComponent::Sql(c) => c.operation_signatures(),
163      HighLevelComponent::HttpClient(c) => c.operation_signatures(),
164    }
165  }
166}
167
168/// The kinds of collections that can operate in a flow.
169#[derive(Debug, Clone, PartialEq, derive_asset_container::AssetManager, serde::Serialize)]
170#[asset(asset(config::AssetReference))]
171#[must_use]
172#[serde(rename_all = "kebab-case")]
173pub enum ComponentDefinition {
174  #[doc(hidden)]
175  #[asset(skip)]
176  Native(config::components::NativeComponent),
177  /// WebAssembly Collections.
178  #[deprecated(note = "Use ManifestComponent instead")]
179  Wasm(config::components::WasmComponent),
180  /// A component reference.
181  #[asset(skip)]
182  Reference(config::components::ComponentReference),
183  /// Separate microservices that Wick can connect to.
184  #[asset(skip)]
185  GrpcUrl(config::components::GrpcUrlComponent),
186  /// External manifests.
187  Manifest(config::components::ManifestComponent),
188  /// Postgres Component.
189  #[asset(skip)]
190  HighLevelComponent(HighLevelComponent),
191}
192
193#[derive(Debug, Clone, Copy)]
194pub enum ComponentDefinitionKind {
195  Native,
196  Wasm,
197  Reference,
198  GrpcUrl,
199  Manifest,
200  HighLevelComponent,
201}
202
203impl std::fmt::Display for ComponentDefinitionKind {
204  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205    match self {
206      ComponentDefinitionKind::Native => write!(f, "native"),
207      ComponentDefinitionKind::Wasm => write!(f, "wasm"),
208      ComponentDefinitionKind::Reference => write!(f, "reference"),
209      ComponentDefinitionKind::GrpcUrl => write!(f, "grpc-url"),
210      ComponentDefinitionKind::Manifest => write!(f, "manifest"),
211      ComponentDefinitionKind::HighLevelComponent => write!(f, "high-level-component"),
212    }
213  }
214}
215
216impl ComponentDefinition {
217  /// Returns true if the definition is a reference to another component.
218  #[must_use]
219  pub const fn is_reference(&self) -> bool {
220    matches!(self, ComponentDefinition::Reference(_))
221  }
222
223  /// Returns the kind of the component definition.
224  #[must_use]
225  pub const fn kind(&self) -> ComponentDefinitionKind {
226    match self {
227      ComponentDefinition::Native(_) => ComponentDefinitionKind::Native,
228      ComponentDefinition::Wasm(_) => ComponentDefinitionKind::Wasm,
229      ComponentDefinition::Reference(_) => ComponentDefinitionKind::Reference,
230      ComponentDefinition::GrpcUrl(_) => ComponentDefinitionKind::GrpcUrl,
231      ComponentDefinition::Manifest(_) => ComponentDefinitionKind::Manifest,
232      ComponentDefinition::HighLevelComponent(_) => ComponentDefinitionKind::HighLevelComponent,
233    }
234  }
235
236  /// Returns the component config, if it exists
237  #[must_use]
238  pub const fn config(&self) -> Option<&LiquidJsonConfig> {
239    match self {
240      #[allow(deprecated)]
241      ComponentDefinition::Wasm(c) => c.config.as_ref(),
242      ComponentDefinition::GrpcUrl(c) => c.config.as_ref(),
243      ComponentDefinition::Manifest(c) => c.config.as_ref(),
244      ComponentDefinition::Native(_) => None,
245      ComponentDefinition::Reference(_) => None,
246      ComponentDefinition::HighLevelComponent(_) => None,
247    }
248  }
249
250  /// Returns any components this configuration provides to the implementation.
251  #[must_use]
252  pub fn provide(&self) -> Option<&HashMap<String, String>> {
253    match self {
254      #[allow(deprecated)]
255      ComponentDefinition::Wasm(c) => Some(c.provide()),
256      ComponentDefinition::GrpcUrl(_) => None,
257      ComponentDefinition::Manifest(c) => Some(c.provide()),
258      ComponentDefinition::Native(_) => None,
259      ComponentDefinition::Reference(_) => None,
260      ComponentDefinition::HighLevelComponent(_) => None,
261    }
262  }
263
264  /// Returns the component config, if it exists
265  #[must_use]
266  pub fn config_mut(&mut self) -> Option<&mut LiquidJsonConfig> {
267    match self {
268      #[allow(deprecated)]
269      ComponentDefinition::Wasm(c) => c.config.as_mut(),
270      ComponentDefinition::GrpcUrl(c) => c.config.as_mut(),
271      ComponentDefinition::Manifest(c) => c.config.as_mut(),
272      ComponentDefinition::Native(_) => None,
273      ComponentDefinition::Reference(_) => None,
274      ComponentDefinition::HighLevelComponent(_) => None,
275    }
276  }
277
278  /// Returns the component config, if it exists
279  pub fn set_config(&mut self, config: Option<RuntimeConfig>) {
280    match self {
281      #[allow(deprecated)]
282      ComponentDefinition::Wasm(c) => c.config.as_mut().map(|c| c.set_value(config)),
283      ComponentDefinition::GrpcUrl(c) => c.config.as_mut().map(|c| c.set_value(config)),
284      ComponentDefinition::Manifest(c) => c.config.as_mut().map(|c| c.set_value(config)),
285      ComponentDefinition::Native(_) => None,
286      ComponentDefinition::Reference(_) => None,
287      ComponentDefinition::HighLevelComponent(_) => None,
288    };
289  }
290}
291
292impl Renderable for ComponentDefinition {
293  fn render_config(
294    &mut self,
295    source: Option<&Path>,
296    root_config: Option<&RuntimeConfig>,
297    env: Option<&HashMap<String, String>>,
298  ) -> Result<(), ManifestError> {
299    let val = if let Some(config) = self.config() {
300      Some(config.render(source, root_config, None, env, None)?)
301    } else {
302      None
303    };
304    self.set_config(val);
305    Ok(())
306  }
307}
308
309impl OperationSignatures for &ComponentDefinition {
310  fn operation_signatures(&self) -> Vec<wick_interface_types::OperationSignature> {
311    match self {
312      ComponentDefinition::Manifest(c) => c.operation_signatures(),
313      ComponentDefinition::HighLevelComponent(c) => c.operation_signatures(),
314      ComponentDefinition::Native(_) => unreachable!(),
315      ComponentDefinition::Reference(_) => unreachable!(),
316      ComponentDefinition::GrpcUrl(_) => unreachable!(),
317      #[allow(deprecated)]
318      ComponentDefinition::Wasm(_) => unreachable!(),
319    }
320  }
321}
322
323#[derive(Default, Debug)]
324struct StringPair(String, String);
325
326impl<'de> serde::Deserialize<'de> for StringPair {
327  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328  where
329    D: Deserializer<'de>,
330  {
331    struct StringPairVisitor;
332
333    impl<'de> Visitor<'de> for StringPairVisitor {
334      type Value = StringPair;
335
336      fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
337        formatter.write_str("a String pair")
338      }
339
340      fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
341      where
342        V: SeqAccess<'de>,
343      {
344        let s = seq
345          .next_element()?
346          .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
347        let n = seq
348          .next_element()?
349          .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
350
351        // This is very important!
352        while matches!(seq.next_element()?, Some(IgnoredAny)) {
353          // Ignore rest
354        }
355
356        Ok(StringPair(s, n))
357      }
358    }
359
360    deserializer.deserialize_seq(StringPairVisitor)
361  }
362}