Skip to main content

durable_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{ItemFn, parse_macro_input};
4
5/// Marks a function as a durable workflow.
6///
7/// The first parameter must be `ctx: &mut Ctx`. The macro wraps the function
8/// to create/resume a task row and manage the workflow lifecycle.
9///
10/// ```ignore
11/// #[durable::workflow]
12/// async fn my_job(ctx: &mut Ctx, input: String) -> Result<()> {
13///     // ...
14/// }
15/// ```
16#[proc_macro_attribute]
17pub fn workflow(_attr: TokenStream, item: TokenStream) -> TokenStream {
18    let input_fn = parse_macro_input!(item as ItemFn);
19    let fn_name = &input_fn.sig.ident;
20    let fn_name_str = fn_name.to_string();
21    let vis = &input_fn.vis;
22    let sig = &input_fn.sig;
23    let body = &input_fn.block;
24    let attrs = &input_fn.attrs;
25
26    let expanded = quote! {
27        #(#attrs)*
28        #vis #sig {
29            let _workflow_name = #fn_name_str;
30            tracing::info!(workflow = _workflow_name, "workflow started");
31            let _result: Result<_, _> = async { #body }.await;
32            match &_result {
33                Ok(_) => tracing::info!(workflow = _workflow_name, "workflow completed"),
34                Err(e) => tracing::error!(workflow = _workflow_name, error = %e, "workflow failed"),
35            }
36            _result
37        }
38    };
39
40    TokenStream::from(expanded)
41}
42
43/// Marks a function as a durable step.
44///
45/// The first parameter must be `ctx: &Ctx`. The macro wraps the function
46/// to check for saved output, execute if needed, and save the result.
47///
48/// ```ignore
49/// #[durable::step]
50/// async fn fetch_data(ctx: &Ctx, url: &str) -> Result<Data> {
51///     // ...
52/// }
53/// ```
54#[proc_macro_attribute]
55pub fn step(_attr: TokenStream, item: TokenStream) -> TokenStream {
56    let input_fn = parse_macro_input!(item as ItemFn);
57    let fn_name = &input_fn.sig.ident;
58    let fn_name_str = fn_name.to_string();
59    let vis = &input_fn.vis;
60    let sig = &input_fn.sig;
61    let body = &input_fn.block;
62    let attrs = &input_fn.attrs;
63
64    let expanded = quote! {
65        #(#attrs)*
66        #vis #sig {
67            let _step_name = #fn_name_str;
68            tracing::debug!(step = _step_name, "step executing");
69            let _result: Result<_, _> = async { #body }.await;
70            match &_result {
71                Ok(_) => tracing::debug!(step = _step_name, "step completed"),
72                Err(e) => tracing::warn!(step = _step_name, error = %e, "step failed"),
73            }
74            _result
75        }
76    };
77
78    TokenStream::from(expanded)
79}