vector_config_macros/component_name.rs
1use darling::util::path_to_string;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{Attribute, DeriveInput, Error, LitStr, parse_macro_input, spanned::Spanned};
5
6use crate::attrs::{self, path_matches};
7
8pub fn derive_component_name_impl(input: TokenStream) -> TokenStream {
9 let input = parse_macro_input!(input as DeriveInput);
10 let ident = &input.ident;
11
12 // This derive macro has to do a bunch of heavy lifting here, but it mainly boils down to two
13 // things: validating that a name is given for the component (and only one name), and spitting
14 // out a component type-specific error message otherwise.
15 //
16 // Firstly, we want to give contextual error messages to users whenever possible, so that means
17 // that if a user is transitively deriving this macro because they're using
18 // `#[configurable_component(source)]`, we want to let them know that sources must have a name
19 // defined.
20 //
21 // It's easier to do that in the `configurable_component` macro, but errors in attribute macros
22 // lead to the item being annotated essentially being excluded from the compiled output... so
23 // you see the error that the attribute macro emits... and then you see an error anywhere else
24 // some code is trying to reference that struct or enum or what have you. This leads to a large
25 // number of errors that are technically related to the problem at hand, but ultimately are all
26 // solved by fixing the error with the macro usage.
27 //
28 // Derive macros function differently, such that the original item is always left alone and only
29 // new tokens are generated, so we can emit errors without causing more errors, but we've lost
30 // some of the semantic information about the original usage of `configurable_component` by the
31 // time we're here, running _this_ macro.
32 //
33 // To deal with this, we specifically look for component type-specific attributes that define
34 // the name, and higher up in `configurable_component`, we use the appropriate attribute for the
35 // component type at hand. This ends up giving output that follows this pattern:
36 //
37 // `#[configurable_component(source)]` -> `#[source_component]`
38 // `#[configurable_component(source("foo"))]` -> `#[source_component("foo")]`
39 //
40 // This allows us to determine the component type originally passed to `configurable_component`
41 // so that, in the case of the nameless example above, we can generate an error message attached
42 // to the span for `source`, such as: "sources must specify a name (e.g. `source("name")`)"
43 //
44 // Secondly, and finally, we always expect a single one of these helper attributes defining the
45 // name: no more, no less. Even though `configurable_component` should correctly adhere to that,
46 // we still need to go through the motions of verifying it here... which may capture either
47 // someone manually using the derive incorrectly, or an actual bug in our usage via other macros.
48 let mut errors = Vec::new();
49 let mut component_names = input
50 .attrs
51 .iter()
52 .filter_map(|attr| match attr_to_component_name(attr) {
53 Ok(component_name) => component_name,
54 Err(e) => {
55 errors.push(e);
56 None
57 }
58 })
59 .collect::<Vec<_>>();
60
61 if !errors.is_empty() {
62 let mut main_error = errors.remove(0);
63 for error in errors.drain(..) {
64 main_error.combine(error);
65 }
66
67 return main_error.into_compile_error().into();
68 }
69
70 // Any component names we have now have been validated, so we just need to check and make sure
71 // we actually have one, and only one, and spit out the correct errors otherwise.
72 if component_names.is_empty() {
73 return Error::new(
74 ident.span(),
75 "component must have a name defined (e.g. `#[component_name(\"foobar\")]`)",
76 )
77 .into_compile_error()
78 .into();
79 }
80
81 if component_names.len() > 1 {
82 return Error::new(ident.span(), "component cannot have multiple names defined")
83 .into_compile_error()
84 .into();
85 }
86
87 let component_name = component_names.remove(0);
88
89 // We have a single, valid component name, so let's actually spit out our derive.
90 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
91 let derived = quote! {
92 impl #impl_generics #ident #ty_generics #where_clause {
93 pub(super) const NAME: &'static str = #component_name;
94 }
95
96 impl #impl_generics ::vector_config::NamedComponent for #ident #ty_generics #where_clause {
97 fn get_component_name(&self) -> &'static str {
98 #component_name
99 }
100 }
101 };
102 derived.into()
103}
104
105fn attr_to_component_name(attr: &Attribute) -> Result<Option<String>, Error> {
106 // First, filter out anything that isn't ours.
107 if !path_matches(
108 attr.path(),
109 &[
110 attrs::API_COMPONENT,
111 attrs::ENRICHMENT_TABLE_COMPONENT,
112 attrs::GLOBAL_OPTION_COMPONENT,
113 attrs::PROVIDER_COMPONENT,
114 attrs::SINK_COMPONENT,
115 attrs::SOURCE_COMPONENT,
116 attrs::TRANSFORM_COMPONENT,
117 attrs::SECRETS_COMPONENT,
118 ],
119 ) {
120 return Ok(None);
121 }
122
123 // Reconstruct the original attribute path (i.e. `source`) from our marker version of it (i.e.
124 // `source_component`), so that any error message we emit is contextually relevant.
125 let path_str = path_to_string(attr.path());
126 let component_type_attr = path_str.replace("_component", "");
127 let component_type = component_type_attr.replace('_', " ");
128
129 // Make sure the attribute actually has inner tokens. If it doesn't, this means they forgot
130 // entirely to specify a component name, and we want to give back a meaningful error that looks
131 // correct when applied in the context of `#[configurable_component(...)]`.
132 if attr.meta.require_list().is_err() {
133 return Err(Error::new(
134 attr.span(),
135 format!(
136 "{component_type}s must have a name specified (e.g. `{component_type_attr}(\"my_component\")`)"
137 ),
138 ));
139 }
140
141 // Now try and parse the helper attribute as a literal string, which is the only valid form.
142 // After that, make sure it's actually valid according to our naming rules.
143 attr.parse_args::<LitStr>()
144 .map_err(|_| {
145 Error::new(
146 attr.span(),
147 format!(
148 "expected a string literal for the {component_type} name (i.e. `{component_type_attr}(\"...\")`)"
149 ),
150 )
151 })
152 .and_then(|component_name| {
153 let component_name_str = component_name.value();
154 check_component_name_validity(&component_name_str)
155 .map_err(|e| Error::new(component_name.span(), e))
156 .map(|()| Some(component_name_str))
157 })
158}
159
160fn check_component_name_validity(component_name: &str) -> Result<(), String> {
161 // In a nutshell, component names must contain only lowercase ASCII alphabetic characters, or
162 // numbers, or underscores.
163
164 if component_name.is_empty() {
165 return Err("component name must be non-empty".to_string());
166 }
167
168 // We only support ASCII names, so get that out of the way.
169 if !component_name.is_ascii() {
170 return Err("component names may only contain ASCII characters".to_string());
171 }
172
173 // Now, we blindly try and convert the given component name into the correct format, and
174 // if the result doesn't match the input, then we know the input is invalid... but then we also
175 // have an example string to show in the error to explain what the user needs to specify.
176 let component_name_converted = component_name
177 .chars()
178 .flat_map(|c| c.to_lowercase())
179 .map(|c| if !c.is_ascii_alphanumeric() { '_' } else { c })
180 .collect::<String>();
181
182 if component_name == component_name_converted {
183 Ok(())
184 } else {
185 Err(format!(
186 "component names must be lowercase, and contain only letters, numbers, and underscores (e.g. \"{component_name_converted}\")"
187 ))
188 }
189}