bolt_attribute_bolt_account/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::spanned::Spanned;
4use syn::{parse_macro_input, parse_quote, Attribute, DeriveInput, Lit, Meta, NestedMeta};
5
6/// This BoltAccount attribute is used to automatically generate the seed and size functions
7///
8/// The component_id define the seed used to generate the PDA which stores the component data.
9/// The macro also adds the InitSpace and Default derives to the struct.
10///
11/// #[account]
12/// #[bolt_account(component_id = "bolt-position")]
13/// pub struct Position {
14///     pub x: i64,
15///     pub y: i64,
16///     pub z: i64,
17/// }
18/// ```
19#[proc_macro_attribute]
20pub fn bolt_account(attr: TokenStream, item: TokenStream) -> TokenStream {
21    let attr = parse_macro_input!(attr as Meta);
22    let mut input = parse_macro_input!(item as DeriveInput);
23
24    let component_id_value = match attr {
25        Meta::NameValue(meta_name_value) if meta_name_value.path.is_ident("component_id") => {
26            if let Lit::Str(lit) = meta_name_value.lit {
27                Some(lit.value())
28            } else {
29                None
30            }
31        }
32        Meta::List(meta) => meta.nested.into_iter().find_map(|nested_meta| {
33            if let NestedMeta::Meta(Meta::NameValue(meta_name_value)) = nested_meta {
34                if meta_name_value.path.is_ident("component_id") {
35                    if let Lit::Str(lit) = meta_name_value.lit {
36                        Some(lit.value())
37                    } else {
38                        None
39                    }
40                } else {
41                    None
42                }
43            } else {
44                None
45            }
46        }),
47        _ => {
48            let error = syn::Error::new(attr.span(), "Missing required attribute `component_id`");
49            return error.to_compile_error().into();
50        }
51    };
52
53    let component_id_value = match component_id_value {
54        Some(value) => value,
55        None => {
56            let error = syn::Error::new(input.span(), "The `component_id` attribute is required");
57            return error.to_compile_error().into();
58        }
59    };
60
61    let additional_derives: Attribute = parse_quote! { #[derive(InitSpace, Default)] };
62    input.attrs.push(additional_derives);
63
64    let name = &input.ident;
65    let expanded = quote! {
66        #input
67
68        #[automatically_derived]
69        impl ComponentTraits for #name {
70            fn seed() -> &'static [u8] {
71                #component_id_value.as_bytes()
72            }
73
74            fn size() -> usize {
75                8 + <#name>::INIT_SPACE
76            }
77        }
78    };
79    expanded.into()
80}