durable_lambda_trait/handler.rs
1//! DurableHandler trait definition and runner.
2//!
3//! Provide the [`DurableHandler`] trait and [`run`] entry point for trait-based
4//! durable Lambda handlers (FR33). Internally wires up `lambda_runtime`, AWS config,
5//! and `DurableContext` creation so users never interact with these directly.
6
7use std::sync::Arc;
8
9use durable_lambda_core::backend::RealBackend;
10use durable_lambda_core::context::DurableContext;
11use durable_lambda_core::error::DurableError;
12use durable_lambda_core::event::parse_invocation;
13use durable_lambda_core::response::wrap_handler_result;
14use lambda_runtime::{service_fn, LambdaEvent};
15
16use crate::context::TraitContext;
17
18/// Trait for defining durable Lambda handlers with a structured, object-oriented approach.
19///
20/// Implement this trait on your struct to define handler logic. The [`handle`](DurableHandler::handle)
21/// method receives the deserialized user event payload and a [`TraitContext`] with access
22/// to all durable operations (step, wait, invoke, parallel, etc.).
23///
24/// Use [`run`] to wire up the Lambda runtime — you never interact with `lambda_runtime` directly.
25///
26/// # Examples
27///
28/// ```no_run
29/// use durable_lambda_trait::prelude::*;
30/// use async_trait::async_trait;
31///
32/// struct OrderProcessor;
33///
34/// #[async_trait]
35/// impl DurableHandler for OrderProcessor {
36/// async fn handle(
37/// &self,
38/// event: serde_json::Value,
39/// mut ctx: TraitContext,
40/// ) -> Result<serde_json::Value, DurableError> {
41/// let result: Result<i32, String> = ctx.step("validate", || async {
42/// Ok(42)
43/// }).await?;
44/// Ok(serde_json::json!({"result": result.unwrap()}))
45/// }
46/// }
47///
48/// #[tokio::main]
49/// async fn main() -> Result<(), lambda_runtime::Error> {
50/// durable_lambda_trait::run(OrderProcessor).await
51/// }
52/// ```
53#[async_trait::async_trait]
54pub trait DurableHandler: Send + Sync + 'static {
55 /// Handle a durable Lambda invocation.
56 ///
57 /// # Arguments
58 ///
59 /// * `event` — The deserialized user event payload
60 /// * `ctx` — A [`TraitContext`] providing access to all durable operations
61 ///
62 /// # Errors
63 ///
64 /// Return [`DurableError`] on failure. The runtime converts this to a Lambda error response.
65 async fn handle(
66 &self,
67 event: serde_json::Value,
68 ctx: TraitContext,
69 ) -> Result<serde_json::Value, DurableError>;
70}
71
72/// Run a durable Lambda handler using the trait-based approach.
73///
74/// This is the single entry point for trait-based durable Lambdas. It:
75/// 1. Initializes AWS configuration and creates a Lambda client
76/// 2. Creates a [`RealBackend`] for durable execution API calls
77/// 3. Registers with `lambda_runtime` to receive invocations
78/// 4. On each invocation, extracts durable execution metadata from the event,
79/// creates a [`TraitContext`], and calls [`DurableHandler::handle`]
80///
81/// # Arguments
82///
83/// * `handler` — A struct implementing [`DurableHandler`]
84///
85/// # Errors
86///
87/// Returns `lambda_runtime::Error` if the Lambda runtime fails to start or
88/// encounters a fatal error.
89///
90/// # Examples
91///
92/// ```no_run
93/// use durable_lambda_trait::prelude::*;
94/// use async_trait::async_trait;
95///
96/// struct MyHandler;
97///
98/// #[async_trait]
99/// impl DurableHandler for MyHandler {
100/// async fn handle(
101/// &self,
102/// event: serde_json::Value,
103/// mut ctx: TraitContext,
104/// ) -> Result<serde_json::Value, DurableError> {
105/// let result: Result<i32, String> = ctx.step("process", || async {
106/// Ok(42)
107/// }).await?;
108/// Ok(serde_json::json!({"done": true}))
109/// }
110/// }
111///
112/// #[tokio::main]
113/// async fn main() -> Result<(), lambda_runtime::Error> {
114/// durable_lambda_trait::run(MyHandler).await
115/// }
116/// ```
117pub async fn run<H: DurableHandler>(handler: H) -> Result<(), lambda_runtime::Error> {
118 let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
119 let client = aws_sdk_lambda::Client::new(&config);
120 let backend = Arc::new(RealBackend::new(client));
121
122 let handler = Arc::new(handler);
123
124 lambda_runtime::run(service_fn(|event: LambdaEvent<serde_json::Value>| {
125 let backend = backend.clone();
126 let handler = handler.clone();
127 async move {
128 let (payload, _lambda_ctx) = event.into_parts();
129
130 // Parse all durable execution fields from the Lambda event.
131 let invocation = parse_invocation(&payload)
132 .map_err(Box::<dyn std::error::Error + Send + Sync>::from)?;
133
134 // Create DurableContext and wrap in TraitContext.
135 let durable_ctx = DurableContext::new(
136 backend,
137 invocation.durable_execution_arn,
138 invocation.checkpoint_token,
139 invocation.operations,
140 invocation.next_marker,
141 )
142 .await
143 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
144
145 let trait_ctx = TraitContext::new(durable_ctx);
146
147 // Call the handler's handle method.
148 let result = handler.handle(invocation.user_event, trait_ctx).await;
149
150 // Wrap the result in the durable execution invocation output envelope.
151 wrap_handler_result(result)
152 }
153 }))
154 .await
155}