tauri_plugin_async_wrapper/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, ItemFn};
5
6/// A procedural macro that wraps a function with an asynchronous runtime using `spawn_blocking`.
7/// This macro converts a synchronous function into an asynchronous one and registers it as a Tauri command.
8///
9/// # Example
10/// ```rust
11/// #[async_wrapper]
12/// pub fn example_function(x: i32) -> Result<String, String> {
13///     Ok(format!("Result: {}", x))
14/// }
15/// ```
16/// After applying the macro, the function will be accessible as an asynchronous Tauri command.
17#[proc_macro_attribute]
18pub fn async_wrapper(_attr: TokenStream, item: TokenStream) -> TokenStream {
19    // Parse the input function definition
20    let input = parse_macro_input!(item as ItemFn);
21    // Generate the asynchronous wrapper for the input function
22    generate_async_wrapper(&input).into()
23}
24
25/// Generates an asynchronous wrapper for a given synchronous function.
26/// The wrapped function runs on a separate thread to avoid blocking the main thread.
27///
28/// # Arguments
29/// * `input` - A reference to the parsed function definition.
30///
31/// # Returns
32/// A `proc_macro2::TokenStream` containing the generated asynchronous function.
33fn generate_async_wrapper(input: &ItemFn) -> proc_macro2::TokenStream {
34    let fn_name = &input.sig.ident; // Function name
35    let fn_inputs = &input.sig.inputs; // Function arguments
36    let fn_output = &input.sig.output; // Function return type
37    let fn_block = &input.block; // Function body
38
39    // Generate the asynchronous wrapper code
40    quote! {
41        #[tauri::command]
42        pub async fn #fn_name(#fn_inputs) #fn_output {
43            tauri::async_runtime::spawn_blocking(move || {
44                #fn_block
45            })
46            .await
47            .map_err(|e| e.to_string())?
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use proc_macro2::TokenStream as TokenStream2;
56    use quote::quote;
57    use syn::parse_quote;
58
59    /// Tests the `async_wrapper` macro by validating the generated asynchronous wrapper function.
60    #[test]
61    fn test_async_wrapper() {
62        // Simulate a user-defined input function
63        let input_fn: ItemFn = parse_quote! {
64            pub fn test_function(x: i32, y: String) -> Result<String, String> {
65                Ok(format!("{} {}", x, y))
66            }
67        };
68
69        // Generate the asynchronous wrapper
70        let output = generate_async_wrapper(&input_fn);
71
72        // Expected output for the asynchronous wrapper
73        let expected_output: TokenStream2 = quote! {
74            #[tauri::command]
75            pub async fn test_function(x: i32, y: String) -> Result<String, String> {
76                tauri::async_runtime::spawn_blocking(move || {
77                    { Ok(format!("{} {}", x, y)) }
78                })
79                .await
80                .map_err(|e| e.to_string())?
81            }
82        };
83
84        // Verify that the generated code matches the expected output
85        assert_eq!(output.to_string(), expected_output.to_string());
86    }
87}