durable-lambda-closure 1.2.0

Closure-native API style for AWS Lambda durable execution workflows
Documentation
//! Closure-based handler registration.
//!
//! Provide the [`run`] entry point for closure-native durable Lambda handlers (FR34, FR47).
//! Internally wires up `lambda_runtime`, AWS config, and `DurableContext` creation
//! so users never interact with these directly.

use std::future::Future;
use std::sync::Arc;

use durable_lambda_core::backend::RealBackend;
use durable_lambda_core::context::DurableContext;
use durable_lambda_core::error::DurableError;
use durable_lambda_core::event::parse_invocation;
use durable_lambda_core::response::wrap_handler_result;
use lambda_runtime::{service_fn, LambdaEvent};

use crate::context::ClosureContext;

/// Run a durable Lambda handler using the closure-native approach.
///
/// This is the single entry point for closure-native durable Lambdas. It:
/// 1. Initializes AWS configuration and creates a Lambda client
/// 2. Creates a [`RealBackend`] for durable execution API calls
/// 3. Registers with `lambda_runtime` to receive invocations
/// 4. On each invocation, extracts durable execution metadata from the event,
///    creates a [`ClosureContext`], and calls the user handler
///
/// The handler function receives the deserialized user event payload and an
/// owned [`ClosureContext`] (take it as `mut` to call operations), and returns
/// a JSON result or a [`DurableError`].
///
/// # Arguments
///
/// * `handler` — An async function taking the user event and a `ClosureContext`,
///   returning `Result<serde_json::Value, DurableError>`
///
/// # Errors
///
/// Returns `lambda_runtime::Error` if the Lambda runtime fails to start or
/// encounters a fatal error.
///
/// # Examples
///
/// ```no_run
/// use durable_lambda_closure::prelude::*;
///
/// async fn handler(
///     event: serde_json::Value,
///     mut ctx: ClosureContext,
/// ) -> Result<serde_json::Value, DurableError> {
///     let result: Result<i32, String> = ctx.step("validate", || async {
///         Ok(42)
///     }).await?;
///     Ok(serde_json::json!({"result": result.unwrap()}))
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), lambda_runtime::Error> {
///     durable_lambda_closure::run(handler).await
/// }
/// ```
pub async fn run<F, Fut>(handler: F) -> Result<(), lambda_runtime::Error>
where
    F: Fn(serde_json::Value, ClosureContext) -> Fut + Send + Sync + 'static,
    Fut: Future<Output = Result<serde_json::Value, DurableError>> + Send,
{
    let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
    let client = aws_sdk_lambda::Client::new(&config);
    let backend = Arc::new(RealBackend::new(client));

    lambda_runtime::run(service_fn(|event: LambdaEvent<serde_json::Value>| {
        let backend = backend.clone();
        let handler = &handler;
        async move {
            let (payload, _lambda_ctx) = event.into_parts();

            // Parse all durable execution fields from the Lambda event.
            let invocation = parse_invocation(&payload)
                .map_err(Box::<dyn std::error::Error + Send + Sync>::from)?;

            // Create DurableContext and wrap in ClosureContext.
            let durable_ctx = DurableContext::new(
                backend,
                invocation.durable_execution_arn,
                invocation.checkpoint_token,
                invocation.operations,
                invocation.next_marker,
            )
            .await
            .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;

            let closure_ctx = ClosureContext::new(durable_ctx);

            // Call the user handler with owned context.
            let result = handler(invocation.user_event, closure_ctx).await;

            // Wrap the result in the durable execution invocation output envelope.
            // The durable execution service requires {"Status": "SUCCEEDED/FAILED/PENDING", ...}
            // rather than a plain JSON response.
            wrap_handler_result(result)
        }
    }))
    .await
}