durable-lambda-macro 1.2.0

Proc-macro for zero-boilerplate AWS Lambda durable execution handler registration
Documentation

durable-lambda-macro

Proc-macro for zero-boilerplate AWS Lambda durable execution handler registration.

Docs.rs Crates.io License: MIT OR Apache-2.0

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], 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:

[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:

  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:

// 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

https://github.com/pgdad/durable-rust