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}