Skip to main content

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}