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}