durable_execution_sdk/traits.rs
1//! Trait aliases for common bounds in the AWS Durable Execution SDK.
2//!
3//! This module provides trait aliases that simplify common trait bound combinations
4//! used throughout the SDK. These aliases make function signatures more readable
5//! and maintainable while preserving full type safety.
6//!
7//! # Overview
8//!
9//! The SDK frequently requires types to implement multiple traits for serialization,
10//! thread safety, and lifetime requirements. Instead of repeating these bounds
11//! everywhere, this module provides convenient trait aliases.
12//!
13//! # Available Trait Aliases
14//!
15//! - [`DurableValue`]: For values that can be durably stored and retrieved
16//! - [`StepFn`]: For step function closures
17//!
18//! # Example
19//!
20//! ```rust
21//! use durable_execution_sdk::traits::{DurableValue, StepFn};
22//! use durable_execution_sdk::handlers::StepContext;
23//! use durable_execution_sdk::DurableError;
24//!
25//! // Using DurableValue in a generic function
26//! fn process_value<T: DurableValue>(value: T) -> String {
27//! // T is guaranteed to be Serialize + DeserializeOwned + Send + Sync + 'static
28//! serde_json::to_string(&value).unwrap_or_default()
29//! }
30//!
31//! // Using StepFn for step function bounds
32//! fn execute_step<T, F>(func: F) -> Result<T, DurableError>
33//! where
34//! T: DurableValue,
35//! F: StepFn<T>,
36//! {
37//! // F is guaranteed to be FnOnce(StepContext) -> Result<T, Box<dyn Error + Send + Sync>> + Send
38//! todo!()
39//! }
40//! ```
41
42use serde::{de::DeserializeOwned, Serialize};
43
44use crate::handlers::StepContext;
45
46/// Trait alias for values that can be durably stored and retrieved.
47///
48/// This trait combines the necessary bounds for serialization, deserialization,
49/// and thread-safe sending across async boundaries. Any type implementing
50/// `DurableValue` can be:
51///
52/// - Serialized to JSON for checkpointing
53/// - Deserialized from JSON during replay
54/// - Safely sent between threads
55///
56/// # Equivalent Bounds
57///
58/// `DurableValue` is equivalent to:
59/// ```text
60/// Serialize + DeserializeOwned + Send
61/// ```
62///
63/// # Blanket Implementation
64///
65/// This trait is automatically implemented for all types that satisfy the bounds.
66/// You don't need to implement it manually.
67///
68/// # Example
69///
70/// ```rust
71/// use durable_execution_sdk::traits::DurableValue;
72/// use serde::{Deserialize, Serialize};
73///
74/// // This type automatically implements DurableValue
75/// #[derive(Debug, Clone, Serialize, Deserialize)]
76/// struct OrderResult {
77/// order_id: String,
78/// status: String,
79/// total: f64,
80/// }
81///
82/// // Use in generic functions
83/// fn store_result<T: DurableValue>(result: T) {
84/// let json = serde_json::to_string(&result).unwrap();
85/// println!("Storing: {}", json);
86/// }
87///
88/// let result = OrderResult {
89/// order_id: "ORD-123".to_string(),
90/// status: "completed".to_string(),
91/// total: 99.99,
92/// };
93/// store_result(result);
94/// ```
95///
96/// # Requirements
97///
98/// - 2.1: THE SDK SHALL provide a `DurableValue` trait alias for `Serialize + DeserializeOwned + Send + Sync + 'static`
99/// - 2.4: THE SDK SHALL document trait aliases with examples showing equivalent expanded bounds
100pub trait DurableValue: Serialize + DeserializeOwned + Send {}
101
102/// Blanket implementation for all types meeting the bounds.
103impl<T> DurableValue for T where T: Serialize + DeserializeOwned + Send {}
104
105/// Trait alias for step function bounds.
106///
107/// This trait represents the bounds required for closures passed to the
108/// `DurableContext::step` method. A `StepFn<T>` is a function that:
109///
110/// - Takes a `StepContext` parameter
111/// - Returns `Result<T, Box<dyn Error + Send + Sync>>`
112/// - Can be sent between threads (`Send`)
113///
114/// # Equivalent Bounds
115///
116/// `StepFn<T>` is equivalent to:
117/// ```text
118/// FnOnce(StepContext) -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send
119/// ```
120///
121/// # Blanket Implementation
122///
123/// This trait is automatically implemented for all closures and functions
124/// that satisfy the bounds. You don't need to implement it manually.
125///
126/// # Example
127///
128/// ```rust
129/// use durable_execution_sdk::traits::StepFn;
130/// use durable_execution_sdk::handlers::StepContext;
131///
132/// // A closure that implements StepFn<i32>
133/// let step_fn = |_ctx: StepContext| -> Result<i32, Box<dyn std::error::Error + Send + Sync>> {
134/// Ok(42)
135/// };
136///
137/// // A named function that implements StepFn<String>
138/// fn process_data(ctx: StepContext) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
139/// Ok(format!("Processed by operation {}", ctx.operation_id))
140/// }
141///
142/// // Use in generic functions
143/// fn execute<T, F: StepFn<T>>(func: F) {
144/// // func can be called with a StepContext
145/// }
146///
147/// execute(step_fn);
148/// execute(process_data);
149/// ```
150///
151/// # Requirements
152///
153/// - 2.2: THE SDK SHALL provide a `StepFn` trait alias for step function bounds
154/// - 2.4: THE SDK SHALL document trait aliases with examples showing equivalent expanded bounds
155pub trait StepFn<T>:
156 FnOnce(StepContext) -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send
157{
158}
159
160/// Blanket implementation for all closures meeting the bounds.
161impl<T, F> StepFn<T> for F where
162 F: FnOnce(StepContext) -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send
163{
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use serde::{Deserialize, Serialize};
170
171 // Test that common types implement DurableValue
172 #[test]
173 fn test_durable_value_primitives() {
174 fn assert_durable_value<T: DurableValue>() {}
175
176 assert_durable_value::<i32>();
177 assert_durable_value::<i64>();
178 assert_durable_value::<u32>();
179 assert_durable_value::<u64>();
180 assert_durable_value::<f32>();
181 assert_durable_value::<f64>();
182 assert_durable_value::<bool>();
183 assert_durable_value::<String>();
184 assert_durable_value::<()>();
185 }
186
187 #[test]
188 fn test_durable_value_collections() {
189 fn assert_durable_value<T: DurableValue>() {}
190
191 assert_durable_value::<Vec<i32>>();
192 assert_durable_value::<Vec<String>>();
193 assert_durable_value::<std::collections::HashMap<String, i32>>();
194 assert_durable_value::<Option<String>>();
195 }
196
197 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
198 struct TestStruct {
199 id: String,
200 value: i32,
201 }
202
203 #[test]
204 fn test_durable_value_custom_struct() {
205 fn assert_durable_value<T: DurableValue>() {}
206 assert_durable_value::<TestStruct>();
207 }
208
209 #[test]
210 fn test_durable_value_serialization() {
211 fn serialize_value<T: DurableValue>(value: &T) -> String {
212 serde_json::to_string(value).unwrap()
213 }
214
215 fn deserialize_value<T: DurableValue>(json: &str) -> T {
216 serde_json::from_str(json).unwrap()
217 }
218
219 let original = TestStruct {
220 id: "test-123".to_string(),
221 value: 42,
222 };
223
224 let json = serialize_value(&original);
225 let restored: TestStruct = deserialize_value(&json);
226
227 assert_eq!(original, restored);
228 }
229
230 #[test]
231 fn test_step_fn_closure() {
232 fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
233
234 // Test with a simple closure
235 let closure =
236 |_ctx: StepContext| -> Result<i32, Box<dyn std::error::Error + Send + Sync>> { Ok(42) };
237 assert_step_fn(closure);
238 }
239
240 #[test]
241 fn test_step_fn_with_capture() {
242 fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
243
244 let captured_value = 100;
245 let closure =
246 move |_ctx: StepContext| -> Result<i32, Box<dyn std::error::Error + Send + Sync>> {
247 Ok(captured_value)
248 };
249 assert_step_fn(closure);
250 }
251
252 #[test]
253 fn test_step_fn_returning_struct() {
254 fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
255
256 let closure =
257 |_ctx: StepContext| -> Result<TestStruct, Box<dyn std::error::Error + Send + Sync>> {
258 Ok(TestStruct {
259 id: "test".to_string(),
260 value: 1,
261 })
262 };
263 assert_step_fn(closure);
264 }
265
266 // Test that StepFn works with named functions
267 fn named_step_function(
268 _ctx: StepContext,
269 ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
270 Ok("result".to_string())
271 }
272
273 #[test]
274 fn test_step_fn_named_function() {
275 fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
276 assert_step_fn(named_step_function);
277 }
278
279 // Test type inference with trait aliases
280 #[test]
281 fn test_type_inference() {
282 fn process<T: DurableValue, F: StepFn<T>>(_f: F) -> &'static str {
283 "processed"
284 }
285
286 // Type inference should work without explicit type annotations
287 let result = process(|_ctx| Ok(42i32));
288 assert_eq!(result, "processed");
289
290 let result = process(|_ctx| Ok("hello".to_string()));
291 assert_eq!(result, "processed");
292 }
293
294 // Test that closures can borrow from environment (no 'static requirement)
295 #[test]
296 fn test_step_fn_borrowing_closure() {
297 fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
298
299 let external_data = "borrowed data".to_string();
300 let closure =
301 |_ctx: StepContext| -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
302 Ok(external_data.clone())
303 };
304 assert_step_fn(closure);
305 }
306}