asynq_macros/lib.rs
1//! # Asynq Macros
2//!
3//! Procedural macros for asynq task handler registration.
4//!
5//! This crate provides attribute macros similar to actix-web's routing macros,
6//! allowing you to register task handlers with a simple attribute syntax.
7//!
8//! ## Examples
9//!
10//! ```ignore
11//! use asynq_macros::task_handler;
12//! use asynq::{task::Task, error::Result};
13//!
14//! #[task_handler("email:send")]
15//! fn handle_email(task: Task) -> Result<()> {
16//! println!("Handling email task");
17//! Ok(())
18//! }
19//! ```
20
21use proc_macro::TokenStream;
22use quote::quote;
23use syn::{parse_macro_input, ItemFn, LitStr};
24
25/// Attribute macro for registering synchronous task handlers
26///
27/// This macro marks a function as a task handler and stores metadata about it.
28/// The function signature and behavior remain unchanged.
29///
30/// # Arguments
31///
32/// * `pattern` - The task type pattern to match (e.g., "email:send", "image:resize")
33///
34/// # Examples
35///
36/// ```ignore
37/// use asynq_macros::task_handler;
38/// use asynq::{task::Task, error::Result};
39///
40/// #[task_handler("email:send")]
41/// fn handle_email(task: Task) -> Result<()> {
42/// println!("Processing email task");
43/// Ok(())
44/// }
45/// ```
46#[proc_macro_attribute]
47pub fn task_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
48 let pattern = parse_macro_input!(attr as LitStr);
49 let input_fn = parse_macro_input!(item as ItemFn);
50
51 let fn_name = &input_fn.sig.ident;
52 let pattern_str = pattern.value();
53
54 // Create a doc comment indicating this is a task handler
55 let doc_comment = format!("Task handler for pattern: `{}`", pattern_str);
56
57 // Generate the pattern constant name directly
58 let pattern_const = quote::format_ident!("__{}_PATTERN", fn_name.to_string().to_uppercase());
59
60 // Generate the handler function with added metadata
61 let expanded = quote! {
62 #[doc = #doc_comment]
63 #[allow(non_upper_case_globals)]
64 #input_fn
65
66 // Create a const string to store the pattern
67 #[doc(hidden)]
68 pub const #pattern_const: &str = #pattern;
69 };
70
71 TokenStream::from(expanded)
72}
73
74/// Attribute macro for registering asynchronous task handlers
75///
76/// This macro marks an async function as a task handler and stores metadata about it.
77/// The function signature and behavior remain unchanged.
78///
79/// # Arguments
80///
81/// * `pattern` - The task type pattern to match (e.g., "email:send", "image:resize")
82///
83/// # Examples
84///
85/// ```ignore
86/// use asynq_macros::task_handler_async;
87/// use asynq::{task::Task, error::Result};
88///
89/// #[task_handler_async("image:resize")]
90/// async fn handle_image_resize(task: Task) -> Result<()> {
91/// println!("Processing image resize task");
92/// Ok(())
93/// }
94/// ```
95#[proc_macro_attribute]
96pub fn task_handler_async(attr: TokenStream, item: TokenStream) -> TokenStream {
97 let pattern = parse_macro_input!(attr as LitStr);
98 let input_fn = parse_macro_input!(item as ItemFn);
99
100 let fn_name = &input_fn.sig.ident;
101 let pattern_str = pattern.value();
102
103 // Create a doc comment indicating this is a task handler
104 let doc_comment = format!("Async task handler for pattern: `{}`", pattern_str);
105
106 // Generate the pattern constant name directly
107 let pattern_const = quote::format_ident!("__{}_PATTERN", fn_name.to_string().to_uppercase());
108
109 // Generate the handler function with added metadata
110 let expanded = quote! {
111 #[doc = #doc_comment]
112 #[allow(non_upper_case_globals)]
113 #input_fn
114
115 // Create a const string to store the pattern
116 #[doc(hidden)]
117 pub const #pattern_const: &str = #pattern;
118 };
119
120 TokenStream::from(expanded)
121}
122
123/// Macro for automatically registering synchronous handlers with ServeMux
124///
125/// This macro collects all handlers defined in the current scope and registers them
126/// with a ServeMux instance.
127///
128/// # Examples
129///
130/// ```ignore
131/// use asynq::register_handlers;
132/// use asynq::serve_mux::ServeMux;
133///
134/// let mut mux = ServeMux::new();
135/// register_handlers!(mux, handle_email, handle_image_resize);
136/// ```
137#[proc_macro]
138pub fn register_handlers(input: TokenStream) -> TokenStream {
139 let input_str = input.to_string();
140 let parts: Vec<&str> = input_str.split(',').map(|s| s.trim()).collect();
141
142 if parts.is_empty() {
143 return TokenStream::from(quote! {
144 compile_error!("register_handlers! requires at least a mux variable and one handler");
145 });
146 }
147
148 let mux_var = parts[0];
149 let mux_ident: proc_macro2::TokenStream = mux_var.parse().unwrap();
150
151 let registrations: Vec<proc_macro2::TokenStream> = parts[1..]
152 .iter()
153 .map(|handler_name| {
154 let handler_ident: proc_macro2::TokenStream = handler_name.parse().unwrap();
155 // Generate the pattern constant name directly
156 let pattern_const = quote::format_ident!("__{}_PATTERN", handler_name.to_uppercase());
157
158 quote! {
159 #mux_ident.handle_func(#pattern_const, #handler_ident);
160 }
161 })
162 .collect();
163
164 TokenStream::from(quote! {
165 {
166 #(#registrations)*
167 }
168 })
169}
170
171/// Macro for automatically registering asynchronous handlers with ServeMux
172///
173/// This macro collects all handlers defined in the current scope and registers them
174/// with a ServeMux instance.
175///
176/// # Examples
177///
178/// ```ignore
179/// use asynq::register_async_handlers;
180/// use asynq::serve_mux::ServeMux;
181///
182/// let mut mux = ServeMux::new();
183/// register_async_handlers!(mux, handle_image, handle_payment);
184/// ```
185#[proc_macro]
186pub fn register_async_handlers(input: TokenStream) -> TokenStream {
187 let input_str = input.to_string();
188 let parts: Vec<&str> = input_str.split(',').map(|s| s.trim()).collect();
189
190 if parts.is_empty() {
191 return TokenStream::from(quote! {
192 compile_error!("register_async_handlers! requires at least a mux variable and one handler");
193 });
194 }
195
196 let mux_var = parts[0];
197 let mux_ident: proc_macro2::TokenStream = mux_var.parse().unwrap();
198
199 let registrations: Vec<proc_macro2::TokenStream> = parts[1..]
200 .iter()
201 .map(|handler_name| {
202 let handler_ident: proc_macro2::TokenStream = handler_name.parse().unwrap();
203 // Generate the pattern constant name directly
204 let pattern_const = quote::format_ident!("__{}_PATTERN", handler_name.to_uppercase());
205
206 quote! {
207 #mux_ident.handle_async_func(#pattern_const, #handler_ident);
208 }
209 })
210 .collect();
211
212 TokenStream::from(quote! {
213 {
214 #(#registrations)*
215 }
216 })
217}