rust_task_queue_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Error};
4
5/// Automatically register a task type with the task registry.
6///
7/// This derive macro generates code that uses the inventory pattern to automatically
8/// register task types at runtime. The task will be registered using the name returned
9/// by its `name()` method.
10///
11/// This macro uses the inventory pattern to automatically register task types at runtime.
12///
13/// # Example
14///
15/// ```rust
16/// #[derive(Debug, Serialize, Deserialize, AutoRegisterTask)]
17/// struct MyTask {
18///     data: String,
19/// }
20///
21/// #[async_trait]
22/// impl Task for MyTask {
23///     async fn execute(&self) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
24///         // task implementation  
25///         use serde::Serialize;
26///         #[derive(Serialize)]
27///         struct Response { status: String }
28///         let response = Response { status: "completed".to_string() };
29///         Ok(rmp_serde::to_vec(&response)?)
30///     }
31///     
32///     fn name(&self) -> &str {
33///         "my_task"
34///     }
35/// }
36/// ```
37#[proc_macro_derive(AutoRegisterTask)]
38pub fn auto_register_task(input: TokenStream) -> TokenStream {
39    let input = parse_macro_input!(input as DeriveInput);
40
41    impl_auto_register_task(&input).unwrap_or_else(|err| err.to_compile_error().into())
42}
43
44fn impl_auto_register_task(input: &DeriveInput) -> Result<TokenStream, Error> {
45    let type_name = &input.ident;
46
47    // Generate the inventory submission code
48    let expanded = quote! {
49        // Submit this task type to the inventory for automatic registration
50        ::rust_task_queue::inventory::submit! {
51            ::rust_task_queue::TaskRegistration {
52                type_name: stringify!(#type_name),
53                register_fn: |registry: &::rust_task_queue::TaskRegistry| -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
54                    // We need to get the task name from the type
55                    // For this we create a temporary instance with Default::default()
56                    // This is a limitation but keeps the implementation simple
57                    let temp_instance = <#type_name as Default>::default();
58                    let task_name = temp_instance.name();
59
60                    registry.register_with_name::<#type_name>(task_name)
61                },
62            }
63        }
64    };
65
66    Ok(expanded.into())
67}
68
69/// Attribute macro for registering tasks with a custom name.
70///
71/// This is useful when you want to register a task with a specific name
72/// rather than using the name returned by the `name()` method.
73///
74/// # Example
75///
76/// ```rust
77/// #[register_task("custom_task_name")]
78/// #[derive(Debug, Serialize, Deserialize)]
79/// struct MyTask {
80///     data: String,
81/// }
82/// ```
83#[proc_macro_attribute]
84pub fn register_task(args: TokenStream, input: TokenStream) -> TokenStream {
85    let task_name =
86        if args.is_empty() {
87            None
88        } else {
89            // Parse the task name from the attribute arguments
90            match syn::parse::<syn::LitStr>(args.clone()) {
91            Ok(lit) => Some(lit.value()),
92            Err(_) => return Error::new_spanned(
93                proc_macro2::TokenStream::from(args),
94                "Expected string literal for task name, e.g., #[register_task(\"my_task_name\")]"
95            ).to_compile_error().into(),
96        }
97        };
98
99    let input = parse_macro_input!(input as DeriveInput);
100
101    impl_register_task_with_name(&input, task_name)
102        .unwrap_or_else(|err| err.to_compile_error().into())
103}
104
105fn impl_register_task_with_name(
106    input: &DeriveInput,
107    custom_name: Option<String>,
108) -> Result<TokenStream, Error> {
109    let type_name = &input.ident;
110
111    let registration_code = if let Some(name) = custom_name {
112        // Use the custom name provided in the attribute
113        quote! {
114            ::rust_task_queue::inventory::submit! {
115                ::rust_task_queue::TaskRegistration {
116                    type_name: stringify!(#type_name),
117                    register_fn: |registry: &::rust_task_queue::TaskRegistry| -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
118                        registry.register_with_name::<#type_name>(#name)
119                    },
120                }
121            }
122        }
123    } else {
124        // Use the name from the Task::name() method
125        quote! {
126            ::rust_task_queue::inventory::submit! {
127                ::rust_task_queue::TaskRegistration {
128                    type_name: stringify!(#type_name),
129                    register_fn: |registry: &::rust_task_queue::TaskRegistry| -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
130                        let temp_instance = <#type_name as Default>::default();
131                        let task_name = temp_instance.name();
132                        registry.register_with_name::<#type_name>(task_name)
133                    },
134                }
135            }
136        }
137    };
138
139    // Include the original struct/enum definition along with the registration
140    let expanded = quote! {
141        #input
142
143        #registration_code
144    };
145
146    Ok(expanded.into())
147}