Skip to content

Commit b809df1

Browse files
authored
chore(config): Convert top-level transforms enum to typetag (#16572)
This replaces the top-level `enum Transforms` into a boxed transform type. Serialization and deserialization are handled by `typetag`, and the `configurable_component` macro is enhanced to build out the necessary table entries to generate the schema bits dynamically from all of the components that are compiled into the current configuration.
1 parent f1bdaf2 commit b809df1

File tree

33 files changed

+253
-208
lines changed

33 files changed

+253
-208
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/vector-config-macros/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ proc-macro = true
99

1010
[dependencies]
1111
darling = { version = "0.13", default-features = false, features = ["suggestions"] }
12+
itertools = { version = "0.10.5", default-features = false }
1213
proc-macro2 = { version = "1.0", default-features = false }
1314
quote = { version = "1.0", default-features = false }
1415
serde_derive_internals = "0.26"

lib/vector-config-macros/src/configurable_component.rs

+50-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use darling::{Error, FromMeta};
2+
use itertools::Itertools as _;
23
use proc_macro::TokenStream;
34
use proc_macro2::{Ident, Span};
45
use quote::{quote, quote_spanned};
@@ -88,6 +89,7 @@ struct TypedComponent {
8889
span: Span,
8990
component_type: ComponentType,
9091
component_name: Option<LitStr>,
92+
description: Option<LitStr>,
9193
}
9294

9395
impl TypedComponent {
@@ -102,6 +104,7 @@ impl TypedComponent {
102104
span: path.span(),
103105
component_type,
104106
component_name: None,
107+
description: None,
105108
})
106109
}
107110

@@ -110,18 +113,24 @@ impl TypedComponent {
110113
/// If the meta list does not have a path that matches a known component type, `None` is
111114
/// returned. Otherwise, `Some(...)` is returned with a valid `TypedComponent`.
112115
fn from_meta_list(ml: &MetaList) -> Option<Self> {
116+
let mut items = ml.nested.iter();
113117
ComponentType::try_from(&ml.path)
114118
.ok()
115-
.map(|component_type| match ml.nested.first() {
116-
Some(NestedMeta::Lit(Lit::Str(component_name))) => {
117-
(component_type, Some(component_name.clone()))
119+
.map(|component_type| {
120+
let component_name = match items.next() {
121+
Some(NestedMeta::Lit(Lit::Str(component_name))) => Some(component_name.clone()),
122+
_ => None,
123+
};
124+
let description = match items.next() {
125+
Some(NestedMeta::Lit(Lit::Str(description))) => Some(description.clone()),
126+
_ => None,
127+
};
128+
Self {
129+
span: ml.span(),
130+
component_type,
131+
component_name,
132+
description,
118133
}
119-
_ => (component_type, None),
120-
})
121-
.map(|(component_type, component_name)| Self {
122-
span: ml.span(),
123-
component_type,
124-
component_name,
125134
})
126135
}
127136

@@ -159,9 +168,28 @@ impl TypedComponent {
159168
}
160169
};
161170

171+
// Derive the label from the component name, but capitalized.
172+
let label = capitalize_words(&component_name.value());
173+
174+
// Derive the logical name from the config type, with the trailing "Config" dropped.
175+
let logical_name = config_ty.to_string();
176+
let logical_name = logical_name.strip_suffix("Config").unwrap_or(&logical_name);
177+
178+
// TODO: Make this an `expect` once all component types have been converted.
179+
let description = self
180+
.description
181+
.as_ref()
182+
.map(LitStr::value)
183+
.unwrap_or_else(|| "This component is missing a description.".into());
184+
162185
quote! {
163186
::inventory::submit! {
164-
#desc_ty::new::<#config_ty>(#component_name)
187+
#desc_ty::new::<#config_ty>(
188+
#component_name,
189+
#label,
190+
#logical_name,
191+
#description,
192+
)
165193
}
166194
}
167195
})
@@ -371,3 +399,15 @@ pub fn configurable_component_impl(args: TokenStream, item: TokenStream) -> Toke
371399

372400
derived.into()
373401
}
402+
403+
fn capitalize(s: &str) -> String {
404+
let mut iter = s.chars();
405+
match iter.next() {
406+
None => String::new(),
407+
Some(first) => first.to_uppercase().collect::<String>() + iter.as_str(),
408+
}
409+
}
410+
411+
fn capitalize_words(s: &str) -> String {
412+
s.split('_').map(capitalize).join(" ")
413+
}

lib/vector-config/src/component/description.rs

+62-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use std::marker::PhantomData;
1+
use std::{cell::RefCell, marker::PhantomData};
22

33
use snafu::Snafu;
44
use toml::Value;
5+
use vector_config_common::attributes::CustomAttribute;
56

67
use super::{ComponentMarker, GenerateConfig};
8+
use crate::schema::{SchemaGenerator, SchemaObject};
9+
use crate::{schema, Configurable, ConfigurableRef, GenerateError, Metadata};
710

811
#[derive(Debug, Snafu, Clone, PartialEq, Eq)]
912
pub enum ExampleError {
@@ -17,7 +20,11 @@ pub enum ExampleError {
1720
/// Description of a component.
1821
pub struct ComponentDescription<T: ComponentMarker + Sized> {
1922
component_name: &'static str,
23+
description: &'static str,
24+
label: &'static str,
25+
logical_name: &'static str,
2026
example_value: fn() -> Option<Value>,
27+
config: ConfigurableRef,
2128
_component_type: PhantomData<T>,
2229
}
2330

@@ -33,11 +40,21 @@ where
3340
/// type `T` and the component name. As such, if `T` is `SourceComponent`, and the name is
3441
/// `stdin`, you would say that the component is a "source called `stdin`".
3542
///
36-
/// The type parameter `C` must be the component's configuration type that implements `GenerateConfig`.
37-
pub const fn new<C: GenerateConfig>(component_name: &'static str) -> Self {
43+
/// The type parameter `C` must be the component's configuration type that implements
44+
/// `Configurable` and `GenerateConfig`.
45+
pub const fn new<C: GenerateConfig + Configurable + 'static>(
46+
component_name: &'static str,
47+
label: &'static str,
48+
logical_name: &'static str,
49+
description: &'static str,
50+
) -> Self {
3851
ComponentDescription {
3952
component_name,
53+
description,
54+
label,
55+
logical_name,
4056
example_value: || Some(C::generate_config()),
57+
config: ConfigurableRef::new::<C>(),
4158
_component_type: PhantomData,
4259
}
4360
}
@@ -67,4 +84,46 @@ where
6784
types.sort_unstable();
6885
types
6986
}
87+
88+
/// Generate a schema object covering all the descriptions of this type.
89+
pub fn generate_schemas(gen: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError> {
90+
let mut descriptions: Vec<_> = inventory::iter::<Self>.into_iter().collect();
91+
descriptions.sort_unstable_by_key(|desc| desc.component_name);
92+
let subschemas: Vec<SchemaObject> = descriptions
93+
.into_iter()
94+
.map(|description| description.generate_schema(gen))
95+
.collect::<Result<_, _>>()?;
96+
Ok(schema::generate_one_of_schema(&subschemas))
97+
}
98+
99+
/// Generate a schema object for this description.
100+
fn generate_schema(
101+
&self,
102+
gen: &RefCell<SchemaGenerator>,
103+
) -> Result<SchemaObject, GenerateError> {
104+
let mut tag_subschema =
105+
schema::generate_const_string_schema(self.component_name.to_string());
106+
let variant_tag_metadata = Metadata::with_description(self.description);
107+
schema::apply_base_metadata(&mut tag_subschema, variant_tag_metadata);
108+
109+
let tag_schema =
110+
schema::generate_internal_tagged_variant_schema("type".to_string(), tag_subschema);
111+
let flattened_subschemas = vec![tag_schema];
112+
113+
let mut field_metadata = Metadata::default();
114+
field_metadata.set_transparent();
115+
let mut subschema =
116+
schema::get_or_generate_schema(&self.config, gen, Some(field_metadata))?;
117+
118+
schema::convert_to_flattened_schema(&mut subschema, flattened_subschemas);
119+
120+
let mut variant_metadata = Metadata::default();
121+
variant_metadata.set_description(self.description);
122+
variant_metadata.add_custom_attribute(CustomAttribute::kv("docs::label", self.label));
123+
variant_metadata
124+
.add_custom_attribute(CustomAttribute::kv("logical_name", self.logical_name));
125+
schema::apply_base_metadata(&mut subschema, variant_metadata);
126+
127+
Ok(subschema)
128+
}
70129
}

lib/vector-config/src/configurable.rs

+22-11
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ use crate::{
1717
/// `Configurable` provides the machinery to allow describing and encoding the shape of a type, recursively, so that by
1818
/// instrumenting all transitive types of the configuration, the schema can be discovered by generating the schema from
1919
/// some root type.
20-
pub trait Configurable
21-
where
22-
Self: Sized,
23-
{
20+
pub trait Configurable {
2421
/// Gets the referenceable name of this value, if any.
2522
///
2623
/// When specified, this implies the value is both complex and standardized, and should be
2724
/// reused within any generated schema it is present in.
28-
fn referenceable_name() -> Option<&'static str> {
25+
fn referenceable_name() -> Option<&'static str>
26+
where
27+
Self: Sized,
28+
{
2929
None
3030
}
3131

@@ -38,12 +38,18 @@ where
3838
/// Maps, by definition, are inherently free-form, and thus inherently optional. Thus, this
3939
/// method should likely not be overridden except for implementing `Configurable` for map
4040
/// types. If you're using it for something else, you are expected to know what you're doing.
41-
fn is_optional() -> bool {
41+
fn is_optional() -> bool
42+
where
43+
Self: Sized,
44+
{
4245
false
4346
}
4447

4548
/// Gets the metadata for this value.
46-
fn metadata() -> Metadata {
49+
fn metadata() -> Metadata
50+
where
51+
Self: Sized,
52+
{
4753
Metadata::default()
4854
}
4955

@@ -54,7 +60,10 @@ where
5460
/// numeric types, there is a limited amount of validation that can occur within the
5561
/// `Configurable` derive macro, and additional validation must happen at runtime when the
5662
/// `Configurable` trait is being used, which this method allows for.
57-
fn validate_metadata(_metadata: &Metadata) -> Result<(), GenerateError> {
63+
fn validate_metadata(_metadata: &Metadata) -> Result<(), GenerateError>
64+
where
65+
Self: Sized,
66+
{
5867
Ok(())
5968
}
6069

@@ -64,12 +73,14 @@ where
6473
///
6574
/// If an error occurs while generating the schema, an error variant will be returned describing
6675
/// the issue.
67-
fn generate_schema(gen: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError>;
76+
fn generate_schema(gen: &RefCell<SchemaGenerator>) -> Result<SchemaObject, GenerateError>
77+
where
78+
Self: Sized;
6879

6980
/// Create a new configurable reference table.
7081
fn as_configurable_ref() -> ConfigurableRef
7182
where
72-
Self: 'static,
83+
Self: Sized + 'static,
7384
{
7485
ConfigurableRef::new::<Self>()
7586
}
@@ -98,7 +109,7 @@ pub struct ConfigurableRef {
98109

99110
impl ConfigurableRef {
100111
/// Create a new configurable reference table.
101-
pub const fn new<T: Configurable + ?Sized + 'static>() -> Self {
112+
pub const fn new<T: Configurable + 'static>() -> Self {
102113
Self {
103114
type_name: std::any::type_name::<T>,
104115
referenceable_name: T::referenceable_name,

src/api/schema/components/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{
2020
filter::{self, filter_items},
2121
relay, sort,
2222
},
23-
config::{ComponentKey, Config, TransformConfig},
23+
config::{ComponentKey, Config},
2424
filter_check,
2525
};
2626
use crate::{config::SourceConfig, topology::schema::merged_definition};

src/components/validation/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod test_case;
66
pub mod util;
77
mod validators;
88

9-
use crate::{sinks::Sinks, sources::Sources, transforms::Transforms};
9+
use crate::{config::BoxedTransform, sinks::Sinks, sources::Sources};
1010

1111
pub use self::resources::*;
1212
#[cfg(feature = "component-validation-runner")]
@@ -43,7 +43,7 @@ pub enum ComponentConfiguration {
4343
Source(Sources),
4444

4545
/// A transform component.
46-
Transform(Transforms),
46+
Transform(BoxedTransform),
4747

4848
/// A sink component.
4949
Sink(Sinks),
@@ -78,7 +78,7 @@ impl ValidationConfiguration {
7878
}
7979

8080
/// Creates a new `ValidationConfiguration` for a transform.
81-
pub fn from_transform<C: Into<Transforms>>(component_name: &'static str, config: C) -> Self {
81+
pub fn from_transform(component_name: &'static str, config: impl Into<BoxedTransform>) -> Self {
8282
Self {
8383
component_name,
8484
component_type: ComponentType::Source,

src/components/validation/runner/config.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ use crate::{
44
util::GrpcAddress,
55
ComponentConfiguration, ComponentType, ValidationConfiguration,
66
},
7-
config::ConfigBuilder,
7+
config::{BoxedTransform, ConfigBuilder},
88
sinks::{vector::VectorConfig as VectorSinkConfig, Sinks},
99
sources::{vector::VectorConfig as VectorSourceConfig, Sources},
1010
test_util::next_addr,
11-
transforms::Transforms,
1211
};
1312

1413
use super::{
@@ -57,7 +56,7 @@ impl TopologyBuilder {
5756
}
5857
}
5958

60-
fn from_transform(transform: Transforms) -> Self {
59+
fn from_transform(transform: BoxedTransform) -> Self {
6160
let (input_edge, input_source) = build_input_edge();
6261
let (output_edge, output_sink) = build_output_edge();
6362

src/config/builder.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ use vector_core::config::GlobalOptions;
1010

1111
use crate::{
1212
enrichment_tables::EnrichmentTables, providers::Providers, secrets::SecretBackends,
13-
sinks::Sinks, sources::Sources, transforms::Transforms,
13+
sinks::Sinks, sources::Sources,
1414
};
1515

1616
#[cfg(feature = "api")]
1717
use super::api;
1818
#[cfg(feature = "enterprise")]
1919
use super::enterprise;
2020
use super::{
21-
compiler, schema, ComponentKey, Config, EnrichmentTableOuter, HealthcheckOptions, SinkOuter,
22-
SourceOuter, TestDefinition, TransformOuter,
21+
compiler, schema, BoxedTransform, ComponentKey, Config, EnrichmentTableOuter,
22+
HealthcheckOptions, SinkOuter, SourceOuter, TestDefinition, TransformOuter,
2323
};
2424

2525
/// A complete Vector configuration.
@@ -275,11 +275,11 @@ impl ConfigBuilder {
275275

276276
// For some feature sets, no transforms are compiled, which leads to no callers using this
277277
// method, and in turn, annoying errors about unused variables.
278-
pub fn add_transform<K: Into<String>, T: Into<Transforms>>(
278+
pub fn add_transform(
279279
&mut self,
280-
key: K,
280+
key: impl Into<String>,
281281
inputs: &[&str],
282-
transform: T,
282+
transform: impl Into<BoxedTransform>,
283283
) {
284284
let inputs = inputs
285285
.iter()

src/config/compiler.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use indexmap::IndexSet;
22

33
use super::{
44
builder::ConfigBuilder, graph::Graph, id::Inputs, schema, validation, Config, OutputId,
5-
SourceConfig, TransformConfig,
5+
SourceConfig,
66
};
77

88
pub fn compile(mut builder: ConfigBuilder) -> Result<(Config, Vec<String>), Vec<String>> {

0 commit comments

Comments
 (0)