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 let ty = &input.self_ty;
15
16 let mut queue = quote! { "default" };
18 let mut max_attempts = quote! { 20 };
19
20 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 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 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 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 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 } else {
107 true }
109 });
110
111 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 for derive in user_derives {
125 if !all_derives.contains(&derive) {
126 all_derives.push(derive);
127 }
128 }
129
130 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}