1 stable release
| 1.2.0 | Mar 19, 2026 |
|---|
#2888 in Asynchronous
24KB
307 lines
durable-lambda-macro
Proc-macro for zero-boilerplate AWS Lambda durable execution handler registration.
Overview
durable-lambda-macro is a proc-macro crate that provides the #[durable_execution] attribute macro. It eliminates all Lambda runtime boilerplate by generating a main() function that wires up AWS configuration, the Lambda client, the durable execution backend, and the Lambda runtime event loop.
This is one of four API styles for the durable-rust SDK. All styles produce identical runtime behavior -- they differ only in ergonomics:
| Crate | Style | Best for |
|---|---|---|
durable-lambda-closure |
Closure-native (recommended) | Simplest syntax, no traits or macros |
durable-lambda-macro |
Proc-macro | Zero boilerplate with #[durable_execution] |
durable-lambda-trait |
Trait-based | OOP pattern, shared state via struct fields |
durable-lambda-builder |
Builder-pattern | Most configurable, tracing/error hooks |
Choose the macro style when you want the absolute minimum boilerplate -- just annotate your handler function and the macro does the rest.
Features
#[durable_execution]attribute macro that transforms an async function into a complete Lambda binary- Zero boilerplate -- no
#[tokio::main], noservice_fn, no AWS config setup - Compile-time validation of handler signature: second parameter must be
DurableContext, return type must beResult<_, DurableError> - Generates
main()with Lambda runtime, AWS client,RealBackend, and event parsing - Full access to all 8 durable operations via the generated
DurableContext
Getting Started
Add both durable-lambda-macro and durable-lambda-core to your Cargo.toml:
[dependencies]
durable-lambda-macro = "0.1"
durable-lambda-core = "0.1"
serde_json = "1"
Note: durable-lambda-core is needed for DurableContext and DurableError types. The tokio and lambda_runtime dependencies are handled by the generated code.
Usage
Basic Handler
Annotate an async function with #[durable_execution]:
use durable_lambda_macro::durable_execution;
use durable_lambda_core::context::DurableContext;
use durable_lambda_core::error::DurableError;
#[durable_execution]
async fn handler(
event: serde_json::Value,
mut ctx: DurableContext,
) -> Result<serde_json::Value, DurableError> {
let order: Result<serde_json::Value, String> = ctx.step("validate", || async {
Ok(serde_json::json!({"order_id": 42, "valid": true}))
}).await?;
let payment: Result<String, String> = ctx.step("charge", || async {
Ok("tx-abc-123".to_string())
}).await?;
Ok(serde_json::json!({
"order": order.unwrap(),
"transaction": payment.unwrap(),
}))
}
// main() is generated by the macro -- do not define it yourself
Handler with All Operations
use durable_lambda_macro::durable_execution;
use durable_lambda_core::context::DurableContext;
use durable_lambda_core::error::DurableError;
use durable_lambda_core::types::StepOptions;
#[durable_execution]
async fn handler(
event: serde_json::Value,
mut ctx: DurableContext,
) -> Result<serde_json::Value, DurableError> {
// Step with retries
let result: Result<i32, String> = ctx.step_with_options(
"flaky_call",
StepOptions::new().retries(3).backoff_seconds(2),
|| async { Ok(42) },
).await?;
// Wait
ctx.wait("cooldown", 5).await?;
// Replay-safe logging
ctx.log("processing complete");
Ok(serde_json::json!({"result": result.unwrap()}))
}
What the Macro Generates
The #[durable_execution] attribute preserves your handler function and generates a #[tokio::main] async fn main() that:
- Loads AWS configuration with default credentials
- Creates an
aws_sdk_lambda::Client - Wraps the client in a
RealBackendfor durable execution API calls - Registers with
lambda_runtimeviaservice_fnto receive invocations - On each invocation: parses durable execution metadata from the event, creates a
DurableContext, and calls your handler
Conceptually, the generated code looks like:
// Your handler function is preserved as-is
async fn handler(event: serde_json::Value, mut ctx: DurableContext)
-> Result<serde_json::Value, DurableError>
{
// ... your code ...
}
// This is generated by the macro:
#[tokio::main]
async fn main() -> Result<(), lambda_runtime::Error> {
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let client = aws_sdk_lambda::Client::new(&config);
let backend = std::sync::Arc::new(RealBackend::new(client));
lambda_runtime::run(service_fn(|event: LambdaEvent<serde_json::Value>| {
let backend = backend.clone();
async move {
let invocation = parse_invocation(&payload)?;
let ctx = DurableContext::new(backend, /* ... */).await?;
let result = handler(invocation.user_event, ctx).await;
wrap_handler_result(result)
}
})).await
}
Compile-Time Validations
The macro validates your handler signature at compile time:
- The function must be
async - The second parameter must be
DurableContext - The return type must be
Result<serde_json::Value, DurableError>
If any validation fails, you get a clear compile error pointing to the issue.
Testing
Because the macro generates main(), you cannot directly call the handler in tests. Instead, extract your business logic into a separate function that takes DurableContext and test it with durable-lambda-testing:
// In your handler module:
pub async fn process_order(
event: serde_json::Value,
mut ctx: DurableContext,
) -> Result<serde_json::Value, DurableError> {
let result: Result<i32, String> = ctx.step("validate", || async { Ok(42) }).await?;
Ok(serde_json::json!({"result": result.unwrap()}))
}
// In tests:
use durable_lambda_testing::prelude::*;
#[tokio::test]
async fn test_process_order() {
let (mut ctx, calls, _ops) = MockDurableContext::new()
.with_step_result("validate", r#"42"#)
.build()
.await;
let result = process_order(serde_json::json!({}), ctx).await.unwrap();
assert_eq!(result["result"], 42);
assert_no_checkpoints(&calls).await;
}
API Reference
| Item | Description |
|---|---|
#[durable_execution] |
Attribute macro for handler functions |
DurableContext |
Main context type (from durable-lambda-core) |
DurableError |
SDK error type (from durable-lambda-core) |
StepOptions |
Step configuration (from durable-lambda-core) |
Full API documentation: docs.rs/durable-lambda-macro
License
Licensed under either of MIT or Apache-2.0 at your option.
Repository
Dependencies
~1–1.4MB
~33K SLoC