Skip to main content

durable_lambda_closure/
handler.rs

1//! Closure-based handler registration.
2//!
3//! Provide the [`run`] entry point for closure-native durable Lambda handlers (FR34, FR47).
4//! Internally wires up `lambda_runtime`, AWS config, and `DurableContext` creation
5//! so users never interact with these directly.
6
7use std::future::Future;
8use std::sync::Arc;
9
10use durable_lambda_core::backend::RealBackend;
11use durable_lambda_core::context::DurableContext;
12use durable_lambda_core::error::DurableError;
13use durable_lambda_core::event::parse_invocation;
14use durable_lambda_core::response::wrap_handler_result;
15use lambda_runtime::{service_fn, LambdaEvent};
16
17use crate::context::ClosureContext;
18
19/// Run a durable Lambda handler using the closure-native approach.
20///
21/// This is the single entry point for closure-native durable Lambdas. It:
22/// 1. Initializes AWS configuration and creates a Lambda client
23/// 2. Creates a [`RealBackend`] for durable execution API calls
24/// 3. Registers with `lambda_runtime` to receive invocations
25/// 4. On each invocation, extracts durable execution metadata from the event,
26///    creates a [`ClosureContext`], and calls the user handler
27///
28/// The handler function receives the deserialized user event payload and an
29/// owned [`ClosureContext`] (take it as `mut` to call operations), and returns
30/// a JSON result or a [`DurableError`].
31///
32/// # Arguments
33///
34/// * `handler` — An async function taking the user event and a `ClosureContext`,
35///   returning `Result<serde_json::Value, DurableError>`
36///
37/// # Errors
38///
39/// Returns `lambda_runtime::Error` if the Lambda runtime fails to start or
40/// encounters a fatal error.
41///
42/// # Examples
43///
44/// ```no_run
45/// use durable_lambda_closure::prelude::*;
46///
47/// async fn handler(
48///     event: serde_json::Value,
49///     mut ctx: ClosureContext,
50/// ) -> Result<serde_json::Value, DurableError> {
51///     let result: Result<i32, String> = ctx.step("validate", || async {
52///         Ok(42)
53///     }).await?;
54///     Ok(serde_json::json!({"result": result.unwrap()}))
55/// }
56///
57/// #[tokio::main]
58/// async fn main() -> Result<(), lambda_runtime::Error> {
59///     durable_lambda_closure::run(handler).await
60/// }
61/// ```
62pub async fn run<F, Fut>(handler: F) -> Result<(), lambda_runtime::Error>
63where
64    F: Fn(serde_json::Value, ClosureContext) -> Fut + Send + Sync + 'static,
65    Fut: Future<Output = Result<serde_json::Value, DurableError>> + Send,
66{
67    let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
68    let client = aws_sdk_lambda::Client::new(&config);
69    let backend = Arc::new(RealBackend::new(client));
70
71    lambda_runtime::run(service_fn(|event: LambdaEvent<serde_json::Value>| {
72        let backend = backend.clone();
73        let handler = &handler;
74        async move {
75            let (payload, _lambda_ctx) = event.into_parts();
76
77            // Parse all durable execution fields from the Lambda event.
78            let invocation = parse_invocation(&payload)
79                .map_err(Box::<dyn std::error::Error + Send + Sync>::from)?;
80
81            // Create DurableContext and wrap in ClosureContext.
82            let durable_ctx = DurableContext::new(
83                backend,
84                invocation.durable_execution_arn,
85                invocation.checkpoint_token,
86                invocation.operations,
87                invocation.next_marker,
88            )
89            .await
90            .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
91
92            let closure_ctx = ClosureContext::new(durable_ctx);
93
94            // Call the user handler with owned context.
95            let result = handler(invocation.user_event, closure_ctx).await;
96
97            // Wrap the result in the durable execution invocation output envelope.
98            // The durable execution service requires {"Status": "SUCCEEDED/FAILED/PENDING", ...}
99            // rather than a plain JSON response.
100            wrap_handler_result(result)
101        }
102    }))
103    .await
104}