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}