falco_plugin_derive/
lib.rs1#![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}