ishikari_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::*;
3use syn::{
4    parse_macro_input, AttributeArgs, DeriveInput, Ident, ItemImpl, Lit, Meta, MetaNameValue,
5    NestedMeta,
6};
7
8#[proc_macro_attribute]
9pub fn worker(attr: TokenStream, item: TokenStream) -> TokenStream {
10    let args = parse_macro_input!(attr as AttributeArgs);
11    let input = parse_macro_input!(item as ItemImpl);
12
13    // Get the type being implemented
14    let ty = &input.self_ty;
15
16    // Default values
17    let mut queue = quote! { "default" };
18    let mut max_attempts = quote! { 20 };
19
20    // Parse attribute arguments
21    for arg in args {
22        if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) = arg {
23            let ident = path.get_ident().unwrap().to_string();
24            match ident.as_str() {
25                "queue" => {
26                    if let Lit::Str(s) = lit {
27                        queue = quote! { #s };
28                    }
29                }
30                "max_attempts" => {
31                    if let Lit::Int(i) = lit {
32                        max_attempts = quote! { #i };
33                    }
34                }
35                _ => {}
36            }
37        }
38    }
39
40    // Extract the perform method from the input implementation
41    let mut perform_method = None;
42    let mut backoff_method = None;
43
44    for item in input.items.iter() {
45        if let syn::ImplItem::Method(method) = item {
46            match method.sig.ident.to_string().as_str() {
47                "perform" => perform_method = Some(method.clone()),
48                "backoff" => backoff_method = Some(method.clone()),
49                _ => {}
50            }
51        }
52    }
53
54    // Generate the code
55    let expanded = quote! {
56        #[async_trait::async_trait]
57        #[typetag::serde]
58        impl Worker for #ty {
59            fn queue(&self) -> &'static str {
60                #queue
61            }
62
63            fn max_attempts(&self) -> i32 {
64                #max_attempts
65            }
66
67            #backoff_method
68
69            #perform_method
70        }
71    };
72
73    expanded.into()
74}
75
76#[proc_macro_attribute]
77pub fn job(_attr: TokenStream, item: TokenStream) -> TokenStream {
78    let mut input = parse_macro_input!(item as DeriveInput);
79
80    // Find and remove any existing derive attributes, collecting user derives
81    let mut user_derives = Vec::new();
82    let mut errors = Vec::new();
83
84    input.attrs.retain(|attr| {
85        if attr.path.is_ident("derive") {
86            // Extract user-specified derives
87            if let Ok(Meta::List(list)) = attr.parse_meta() {
88                for nested in &list.nested {
89                    if let NestedMeta::Meta(Meta::Path(path)) = nested {
90                        if let Some(segment) = path.segments.first() {
91                            let ident = segment.ident.to_string();
92                            match ident.as_str() {
93                                "Deserialize" | "Serialize" => {
94                                    errors.push(syn::Error::new(
95                                        segment.ident.span(),
96                                        "The #[ishikari::job] macro automatically derives serde traits. Please remove the manual serde derives.",
97                                    ));
98                                }
99                                _ => user_derives.push(ident),
100                            }
101                        }
102                    }
103                }
104            }
105            false // Remove the original derive attribute
106        } else {
107            true // Keep other attributes
108        }
109    });
110
111    // If we found any errors, return them
112    if !errors.is_empty() {
113        let first_error = errors.remove(0);
114        let error = errors.into_iter().fold(first_error, |mut acc, err| {
115            acc.combine(err);
116            acc
117        });
118        return error.to_compile_error().into();
119    }
120
121    let mut all_derives = vec!["Debug".to_string()];
122
123    // Add user derives that aren't already included
124    for derive in user_derives {
125        if !all_derives.contains(&derive) {
126            all_derives.push(derive);
127        }
128    }
129
130    // Create a new derive attribute with all derives
131    let derive_tokens = all_derives.iter().map(|derive| {
132        let ident = Ident::new(derive, proc_macro2::Span::call_site());
133        quote! { #ident }
134    });
135
136    let expanded = quote! {
137        #[derive(#(#derive_tokens,)* ::serde::Serialize, ::serde::Deserialize)]
138        #[serde(crate = "::ishikari::serde")]
139        #input
140    };
141
142    expanded.into()
143}