Skip to main content

durable_execution_sdk_macros/
lib.rs

1//! Procedural macros for AWS Durable Execution SDK
2//!
3//! This crate provides the `#[durable_execution]` attribute macro
4//! for creating durable Lambda handler functions.
5
6use proc_macro::TokenStream;
7use quote::{format_ident, quote};
8use syn::{parse_macro_input, spanned::Spanned, FnArg, ItemFn, Pat, ReturnType};
9
10/// Attribute macro that transforms an async function into a durable Lambda handler.
11///
12/// This macro wraps your async function to integrate with AWS Lambda's durable execution
13/// service. It handles:
14/// - Parsing `DurableExecutionInvocationInput` from the Lambda event
15/// - Creating `ExecutionState` and `DurableContext` for the handler
16/// - Processing results, errors, and suspend signals
17/// - Returning `DurableExecutionInvocationOutput` with appropriate status
18///
19/// # Function Signature
20///
21/// The decorated function must have the following signature:
22/// ```rust,ignore
23/// async fn handler_name(event: EventType, ctx: DurableContext) -> Result<ResultType, DurableError>
24/// ```
25///
26/// Where:
27/// - `EventType` must implement `serde::Deserialize`
28/// - `ResultType` must implement `serde::Serialize`
29///
30/// # Example
31///
32/// ```rust,ignore
33/// use durable_execution_sdk::{durable_execution, DurableContext, DurableError};
34/// use serde::{Deserialize, Serialize};
35///
36/// #[derive(Deserialize)]
37/// struct MyEvent {
38///     order_id: String,
39/// }
40///
41/// #[derive(Serialize)]
42/// struct MyResult {
43///     status: String,
44/// }
45///
46/// #[durable_execution]
47/// async fn my_handler(event: MyEvent, ctx: DurableContext) -> Result<MyResult, DurableError> {
48///     // Your workflow logic here
49///     let result = ctx.step(|_| Ok("processed".to_string()), None).await?;
50///     Ok(MyResult { status: result })
51/// }
52/// ```
53///
54/// # Generated Code
55///
56/// The macro generates two functions:
57/// 1. An inner async function (`__<name>_inner`) containing the user's original logic
58/// 2. A Lambda handler wrapper that accepts `LambdaEvent<DurableExecutionInvocationInput>`
59///    and delegates to `run_durable_handler` with the inner function
60///
61/// All runtime concerns (event deserialization, state management, context creation,
62/// result/error/suspend handling) are encapsulated in `run_durable_handler`.
63#[proc_macro_attribute]
64pub fn durable_execution(_attr: TokenStream, item: TokenStream) -> TokenStream {
65    let input_fn = parse_macro_input!(item as ItemFn);
66
67    // Validate the function is async
68    if input_fn.sig.asyncness.is_none() {
69        return syn::Error::new(
70            input_fn.sig.fn_token.span(),
71            "durable_execution handler must be an async function",
72        )
73        .to_compile_error()
74        .into();
75    }
76
77    // Extract function components
78    let fn_name = &input_fn.sig.ident;
79    let fn_vis = &input_fn.vis;
80    let fn_block = &input_fn.block;
81    let fn_attrs = &input_fn.attrs;
82
83    // Parse function arguments - expect (event: EventType, ctx: DurableContext)
84    let args: Vec<_> = input_fn.sig.inputs.iter().collect();
85
86    if args.len() != 2 {
87        return syn::Error::new(
88            input_fn.sig.inputs.span(),
89            "durable_execution handler must have exactly 2 arguments: (event: EventType, ctx: DurableContext)"
90        ).to_compile_error().into();
91    }
92
93    // Extract event type from first argument
94    let event_type = match &args[0] {
95        FnArg::Typed(pat_type) => &pat_type.ty,
96        FnArg::Receiver(_) => {
97            return syn::Error::new(
98                args[0].span(),
99                "durable_execution handler cannot have self parameter",
100            )
101            .to_compile_error()
102            .into();
103        }
104    };
105
106    // Extract event parameter name
107    let event_param_name = match &args[0] {
108        FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {
109            Pat::Ident(ident) => &ident.ident,
110            _ => {
111                return syn::Error::new(
112                    pat_type.pat.span(),
113                    "event parameter must be a simple identifier",
114                )
115                .to_compile_error()
116                .into();
117            }
118        },
119        _ => unreachable!(),
120    };
121
122    // Extract context parameter name
123    let ctx_param_name = match &args[1] {
124        FnArg::Typed(pat_type) => match pat_type.pat.as_ref() {
125            Pat::Ident(ident) => &ident.ident,
126            _ => {
127                return syn::Error::new(
128                    pat_type.pat.span(),
129                    "context parameter must be a simple identifier",
130                )
131                .to_compile_error()
132                .into();
133            }
134        },
135        FnArg::Receiver(_) => {
136            return syn::Error::new(
137                args[1].span(),
138                "durable_execution handler cannot have self parameter",
139            )
140            .to_compile_error()
141            .into();
142        }
143    };
144
145    // Extract return type
146    let return_type = match &input_fn.sig.output {
147        ReturnType::Type(_, ty) => ty.as_ref(),
148        ReturnType::Default => {
149            return syn::Error::new(
150                input_fn.sig.output.span(),
151                "durable_execution handler must return Result<T, DurableError>",
152            )
153            .to_compile_error()
154            .into();
155        }
156    };
157
158    // Generate the inner function name
159    let inner_fn_name = format_ident!("__{}_inner", fn_name);
160
161    // Generate the wrapper function — delegates to run_durable_handler
162    let output = quote! {
163        // The inner function containing the user's logic
164        #(#fn_attrs)*
165        async fn #inner_fn_name(
166            #event_param_name: #event_type,
167            #ctx_param_name: ::durable_execution_sdk::DurableContext,
168        ) -> #return_type
169        #fn_block
170
171        // The Lambda handler wrapper — thin delegation to the runtime
172        #fn_vis async fn #fn_name(
173            lambda_event: ::lambda_runtime::LambdaEvent<::durable_execution_sdk::DurableExecutionInvocationInput>,
174        ) -> ::std::result::Result<::durable_execution_sdk::DurableExecutionInvocationOutput, ::lambda_runtime::Error> {
175            ::durable_execution_sdk::run_durable_handler(lambda_event, #inner_fn_name).await
176        }
177    };
178
179    output.into()
180}