Skip to content

Commit 6725661

Browse files
committed
chore(config): Convert top-level transforms enum to typetag
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 ba8c096 commit 6725661

File tree

34 files changed

+256
-206
lines changed

34 files changed

+256
-206
lines changed

Cargo.lock

+2
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/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] }
2323
serde_with = { version = "2.2.0", default-features = false, features = ["std"] }
2424
snafu = { version = "0.7.4", default-features = false }
2525
toml = { version = "0.7.2", default-features = false }
26+
typetag = { version = "0.2.5", default-features = false }
2627
url = { version = "2.3.1", default-features = false, features = ["serde"] }
2728
vector-config-common = { path = "../vector-config-common" }
2829
vector-config-macros = { path = "../vector-config-macros" }

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

+63-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,47 @@ 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 tag_schema = schema::generate_internal_tagged_variant_schema("type".to_string(), {
105+
let mut tag_subschema =
106+
schema::generate_const_string_schema(self.component_name.to_string());
107+
let mut variant_tag_metadata = Metadata::default();
108+
variant_tag_metadata.set_description(self.description);
109+
schema::apply_base_metadata(&mut tag_subschema, variant_tag_metadata);
110+
tag_subschema
111+
});
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, gen, 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("docs::label", self.label));
124+
variant_metadata
125+
.add_custom_attribute(CustomAttribute::kv("logical_name", self.logical_name));
126+
schema::apply_base_metadata(&mut subschema, variant_metadata);
127+
128+
Ok(subschema)
129+
}
70130
}

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

0 commit comments

Comments
 (0)