vector_config/component/
description.rs

1use std::{cell::RefCell, marker::PhantomData};
2
3use snafu::Snafu;
4use toml::Value;
5use vector_config_common::{attributes::CustomAttribute, constants};
6
7use super::{ComponentMarker, GenerateConfig};
8use crate::{
9    Configurable, ConfigurableRef, GenerateError, Metadata, schema,
10    schema::{SchemaGenerator, SchemaObject},
11};
12
13#[derive(Debug, Snafu, Clone, PartialEq, Eq)]
14pub enum ExampleError {
15    #[snafu(display("component '{}' does not exist", component_name))]
16    DoesNotExist { component_name: String },
17}
18
19/// Description of a component.
20pub struct ComponentDescription<T: ComponentMarker + Sized> {
21    component_name: &'static str,
22    description: &'static str,
23    label: &'static str,
24    logical_name: &'static str,
25    example_value: fn() -> Value,
26    config: ConfigurableRef,
27    _component_type: PhantomData<T>,
28}
29
30impl<T> ComponentDescription<T>
31where
32    T: ComponentMarker + Sized + 'static,
33    inventory::iter<ComponentDescription<T>>:
34        std::iter::IntoIterator<Item = &'static ComponentDescription<T>>,
35{
36    /// Creates a new `ComponentDescription`.
37    ///
38    /// This creates a component description for a component identified both by the given component
39    /// type `T` and the component name. As such, if `T` is `SourceComponent`, and the name is
40    /// `stdin`, you would say that the component is a "source called `stdin`".
41    ///
42    /// The type parameter `C` must be the component's configuration type that implements
43    /// `Configurable` and `GenerateConfig`.
44    pub const fn new<C: GenerateConfig + Configurable + 'static>(
45        component_name: &'static str,
46        label: &'static str,
47        logical_name: &'static str,
48        description: &'static str,
49    ) -> Self {
50        ComponentDescription {
51            component_name,
52            description,
53            label,
54            logical_name,
55            example_value: C::generate_config,
56            config: ConfigurableRef::new::<C>(),
57            _component_type: PhantomData,
58        }
59    }
60
61    /// Generates an example configuration for the component with the given component name.
62    ///
63    /// ## Errors
64    ///
65    /// If no component, identified by `T` and the given name, is registered, or if there is an
66    /// error generating the example configuration, an error variant will be returned.
67    pub fn example(component_name: &str) -> Result<Value, ExampleError> {
68        inventory::iter::<ComponentDescription<T>>
69            .into_iter()
70            .find(|t| t.component_name == component_name)
71            .ok_or_else(|| ExampleError::DoesNotExist {
72                component_name: component_name.to_owned(),
73            })
74            .map(|t| (t.example_value)())
75    }
76
77    /// Gets a sorted list of all registered components of the given component type.
78    pub fn types() -> Vec<&'static str> {
79        let mut types = Vec::new();
80        for definition in inventory::iter::<ComponentDescription<T>> {
81            types.push(definition.component_name);
82        }
83        types.sort_unstable();
84        types
85    }
86
87    /// Generate a schema object covering all the descriptions of this type.
88    pub fn generate_schemas(
89        generator: &RefCell<SchemaGenerator>,
90    ) -> Result<SchemaObject, GenerateError> {
91        let mut descriptions: Vec<_> = inventory::iter::<Self>.into_iter().collect();
92        descriptions.sort_unstable_by_key(|desc| desc.component_name);
93        let subschemas: Vec<SchemaObject> = descriptions
94            .into_iter()
95            .map(|description| description.generate_schema(generator))
96            .collect::<Result<_, _>>()?;
97        Ok(schema::generate_one_of_schema(&subschemas))
98    }
99
100    /// Generate a schema object for this description.
101    fn generate_schema(
102        &self,
103        generator: &RefCell<SchemaGenerator>,
104    ) -> Result<SchemaObject, GenerateError> {
105        let mut tag_subschema =
106            schema::generate_const_string_schema(self.component_name.to_string());
107        let variant_tag_metadata = Metadata::with_description(self.description);
108        schema::apply_base_metadata(&mut tag_subschema, variant_tag_metadata);
109
110        let tag_schema =
111            schema::generate_internal_tagged_variant_schema("type".to_string(), tag_subschema);
112        let flattened_subschemas = vec![tag_schema];
113
114        let mut field_metadata = Metadata::default();
115        field_metadata.set_transparent();
116        let mut subschema =
117            schema::get_or_generate_schema(&self.config, generator, Some(field_metadata))?;
118
119        schema::convert_to_flattened_schema(&mut subschema, flattened_subschemas);
120
121        let mut variant_metadata = Metadata::default();
122        variant_metadata.set_description(self.description);
123        variant_metadata.add_custom_attribute(CustomAttribute::kv(
124            constants::DOCS_META_HUMAN_NAME,
125            self.label,
126        ));
127        variant_metadata
128            .add_custom_attribute(CustomAttribute::kv("logical_name", self.logical_name));
129        schema::apply_base_metadata(&mut subschema, variant_metadata);
130
131        Ok(subschema)
132    }
133}