falco_plugin_derive/
lib.rs

1#![doc = include_str!("../README.md")]
2use proc_macro::TokenStream;
3use proc_macro2::Ident;
4use quote::quote;
5use syn::{parse_macro_input, DeriveInput};
6
7fn ident_to_cstr(ident: &Ident) -> syn::LitCStr {
8    let mut name = ident.to_string();
9    name.push('\0');
10    syn::LitCStr::new(
11        std::ffi::CStr::from_bytes_with_nul(name.as_bytes()).unwrap(),
12        ident.span(),
13    )
14}
15
16fn ident_to_bstr(ident: &Ident) -> syn::LitByteStr {
17    let mut name = ident.to_string();
18    name.push('\0');
19    syn::LitByteStr::new(name.as_bytes(), ident.span())
20}
21
22#[proc_macro_derive(Entry)]
23pub fn derive_entry(input: TokenStream) -> TokenStream {
24    let input = parse_macro_input!(input as DeriveInput);
25
26    let syn::Data::Struct(data) = input.data else {
27        return TokenStream::from(
28            syn::Error::new(
29                input.ident.span(),
30                "Only structs with named fields can derive `Entry`",
31            )
32            .to_compile_error(),
33        );
34    };
35
36    let name = &input.ident;
37    let syn::Fields::Named(fields) = data.fields else {
38        return TokenStream::from(
39            syn::Error::new(
40                input.ident.span(),
41                "Only structs with named fields can derive `Entry`",
42            )
43            .to_compile_error(),
44        );
45    };
46
47    let fields = fields.named;
48
49    let static_fields = fields.iter().enumerate().map(|(i, f)| {
50        let field_name = f.ident.as_ref().unwrap();
51        let field_name_bstr = ident_to_bstr(field_name);
52        let tag = format!("{}.{}\0", input.ident, field_name);
53        let field_tag = syn::LitCStr::new(
54            std::ffi::CStr::from_bytes_with_nul(tag.as_bytes()).unwrap(),
55            field_name.span(),
56        );
57
58        let ty = &f.ty;
59        quote!( [#i] #field_tag (#field_name_bstr) as #field_name: #ty)
60    });
61
62    quote!(::falco_plugin::impl_export_table!(
63        for #name
64        {
65            #(#static_fields)*
66        }
67    );)
68    .into()
69}
70
71#[proc_macro_derive(TableMetadata, attributes(entry_type, accessors_mod, name, custom))]
72pub fn derive_table_metadata(input: TokenStream) -> TokenStream {
73    let input = parse_macro_input!(input as DeriveInput);
74    let syn::Data::Struct(data) = input.data else {
75        return TokenStream::from(
76            syn::Error::new(
77                input.ident.span(),
78                "Only structs with named fields can derive `TableMetadata`",
79            )
80            .to_compile_error(),
81        );
82    };
83
84    let name = &input.ident;
85    let syn::Fields::Named(fields) = data.fields else {
86        return TokenStream::from(
87            syn::Error::new(
88                input.ident.span(),
89                "Only structs with named fields can derive `TableMetadata`",
90            )
91            .to_compile_error(),
92        );
93    };
94
95    let fields = fields.named;
96
97    let metadata_macro_args = fields.iter().filter_map(|f| {
98        let field = f.ident.as_ref()?;
99        let field_name = f
100            .attrs
101            .iter()
102            .filter(|a| a.path().is_ident("name"))
103            .filter_map(|a| a.parse_args::<syn::LitCStr>().ok())
104            .next()
105            .unwrap_or_else(|| ident_to_cstr(field));
106
107        let is_custom = f.attrs.iter().any(|f| f.path().is_ident("custom"));
108
109        if is_custom {
110            Some(quote!(add_field(#field, #field_name)))
111        } else {
112            Some(quote!(get_field(#field, #field_name)))
113        }
114    });
115
116    let impl_table_metadata = quote!(falco_plugin::impl_import_table_metadata!(
117        for #name => {
118            #(#metadata_macro_args;)*
119        }
120    ););
121
122    let entry_type = input
123        .attrs
124        .iter()
125        .filter(|a| a.path().is_ident("entry_type"))
126        .filter_map(|a| a.parse_args::<Ident>().ok())
127        .next();
128
129    let accessors_mod = input
130        .attrs
131        .iter()
132        .filter(|a| a.path().is_ident("accessors_mod"))
133        .filter_map(|a| a.parse_args::<Ident>().ok())
134        .next()
135        .unwrap_or_else(|| Ident::new(&format!("__falco_plugin_private_{}", name), name.span()));
136
137    let mut field_traits = Vec::new();
138    let mut field_trait_impls = vec![impl_table_metadata];
139
140    if let Some(entry_type) = entry_type {
141        for f in fields {
142            let Some(field_name) = f.ident.as_ref() else {
143                continue;
144            };
145            let ty = &f.ty;
146
147            let getter_name = Ident::new(&format!("get_{}", field_name), field_name.span());
148            let table_getter_name =
149                Ident::new(&format!("get_{}_by_key", field_name), field_name.span());
150            let setter_name = Ident::new(&format!("set_{}", field_name), field_name.span());
151
152            field_traits.push(quote!(
153                ::falco_plugin::impl_import_table_accessor_traits!(
154                    #field_name: #getter_name, #table_getter_name, #setter_name
155                );
156            ));
157            field_trait_impls.push(quote!(
158                ::falco_plugin::impl_import_table_accessor_impls!(
159                    use #accessors_mod::#field_name;
160                    #field_name(#ty) for #entry_type; meta #name =>
161                        #getter_name, #table_getter_name, #setter_name
162                );
163            ));
164        }
165    }
166
167    quote!(
168        #[allow(non_snake_case)]
169        pub mod #accessors_mod {
170            #(#field_traits)*
171        }
172
173        #(#field_trait_impls)*
174
175        #[allow(unused_imports)]
176        use #accessors_mod::*;
177    )
178    .into()
179}