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}