Skip to main content

act_zero_ext/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{ItemFn, ReturnType, parse_macro_input};
4
5/// Converts a function that returns a `Result<T,E>` into an a function that returns a `ActorResult<Result<T, E>>`
6/// Also works with Option<T> will return `ActorResult<Option<T>>`
7///
8/// Example:
9///
10/// ```rust
11/// use act_zero::*;
12/// pub struct App {}
13///
14/// impl App {
15///     #[act_zero_ext::into_actor_result]
16///     async fn hello(&self, name: String) -> Result<String, Box<dyn std::error::Error>> {
17///         Ok(format!("Hello, {}!", name))
18///     }
19/// }
20/// ```
21///
22/// Will be converted to:
23///
24/// ```rust
25/// use act_zero::*;
26/// pub struct App {}
27///
28/// impl App {
29///     pub async fn hello(&self, name: String) -> ActorResult<Result<String, Box<dyn std::error::Error>>> {
30///         let result = self.do_hello(name).await;
31///         Produces::ok(result)
32///     }
33///
34///     async fn do_hello(&self, name: String) -> Result<String, Box<dyn std::error::Error>> {
35///         Ok(format!("Hello, {}!", name))
36///     }
37/// }
38/// ```
39///
40/// Example with Option:
41///
42/// ```rust
43/// use act_zero::*;
44/// pub struct App {}
45///
46/// impl App {
47///     #[act_zero_ext::into_actor_result]
48///     async fn find_user(&self, id: u64) -> Option<String> {
49///         if id > 0 {
50///             Some(format!("User {}", id))
51///         } else {
52///             None
53///         }
54///     }
55/// }
56/// ```
57///
58/// Will be converted to:
59///
60/// ```rust
61/// use act_zero::*;
62/// pub struct App {}
63///
64/// impl App {
65///     pub async fn find_user(&self, id: u64) -> ActorResult<Option<String>> {
66///         let result = self.do_find_user(id).await;
67///         Produces::ok(result)
68///     }
69///
70///     async fn do_find_user(&self, id: u64) -> Option<String> {
71///         if id > 0 {
72///             Some(format!("User {}", id))
73///         } else {
74///             None
75///         }
76///     }
77/// }
78/// ```
79#[proc_macro_attribute]
80pub fn into_actor_result(_attr: TokenStream, item: TokenStream) -> TokenStream {
81    // Parse the function
82    let input_fn = parse_macro_input!(item as ItemFn);
83
84    // clone for the do_ version
85    let mut do_fn = input_fn.clone();
86
87    // change the function name to do_
88    let fn_name = &input_fn.sig.ident;
89    let do_fn_name = format_ident!("do_{}", fn_name);
90    do_fn.sig.ident = do_fn_name.clone();
91
92    // make the do_ function private
93    do_fn.vis = syn::Visibility::Inherited;
94
95    // extract information for the wrapper function
96    let vis = &input_fn.vis;
97    let asyncness = &input_fn.sig.asyncness;
98    let generics = &input_fn.sig.generics;
99    let inputs = &input_fn.sig.inputs;
100
101    // extract return type for ActorResult wrapper
102    let return_type = match &input_fn.sig.output {
103        ReturnType::Default => quote! { () },
104        ReturnType::Type(_, ty) => quote! { #ty },
105    };
106
107    // get argument names for passing to do_ function
108    let arg_names = inputs
109        .iter()
110        .filter_map(|arg| {
111            if let syn::FnArg::Typed(pat_type) = arg
112                && let syn::Pat::Ident(pat_ident) = &*pat_type.pat
113                && pat_ident.ident != "self"
114            {
115                return Some(&pat_ident.ident);
116            }
117            None
118        })
119        .collect::<Vec<_>>();
120
121    let awaiter = asyncness.is_some().then(|| quote!(.await));
122
123    let wrapper_fn = quote! {
124        #vis async fn #fn_name #generics (#inputs) -> act_zero::ActorResult<#return_type> {
125            let result = self.#do_fn_name(#(#arg_names),*) #awaiter;
126            act_zero::Produces::ok(result)
127        }
128    };
129
130    // generate the final code
131    let result = quote! {
132        #wrapper_fn
133        #do_fn
134    };
135
136    result.into()
137}