# durable-lambda-macro
Proc-macro for zero-boilerplate AWS Lambda durable execution handler registration.
[](https://docs.rs/durable-lambda-macro)
[](https://crates.io/crates/durable-lambda-macro)
[](https://github.com/pgdad/durable-rust#license)
## 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](https://github.com/pgdad/durable-rust) SDK. All styles produce identical runtime behavior -- they differ only in ergonomics:
| [`durable-lambda-closure`](https://crates.io/crates/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`](https://crates.io/crates/durable-lambda-trait) | Trait-based | OOP pattern, shared state via struct fields |
| [`durable-lambda-builder`](https://crates.io/crates/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]`, no `service_fn`, no AWS config setup
- **Compile-time validation** of handler signature: second parameter must be `DurableContext`, return type must be `Result<_, 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`:
```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]`:
```rust
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
```rust
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:
1. Loads AWS configuration with default credentials
2. Creates an `aws_sdk_lambda::Client`
3. Wraps the client in a `RealBackend` for durable execution API calls
4. Registers with `lambda_runtime` via `service_fn` to receive invocations
5. On each invocation: parses durable execution metadata from the event, creates a `DurableContext`, and calls your handler
Conceptually, the generated code looks like:
```rust
// 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`](https://crates.io/crates/durable-lambda-testing):
```rust
// 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
| `#[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](https://docs.rs/durable-lambda-macro)
## License
Licensed under either of [MIT](https://github.com/pgdad/durable-rust/blob/main/LICENSE-MIT) or [Apache-2.0](https://github.com/pgdad/durable-rust/blob/main/LICENSE-APACHE) at your option.
## Repository
[https://github.com/pgdad/durable-rust](https://github.com/pgdad/durable-rust)