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}