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