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/// ```
95pub trait DurableValue: Serialize + DeserializeOwned + Send {}
96
97/// Blanket implementation for all types meeting the bounds.
98impl<T> DurableValue for T where T: Serialize + DeserializeOwned + Send {}
99
100/// Trait alias for step function bounds.
101///
102/// This trait represents the bounds required for closures passed to the
103/// `DurableContext::step` method. A `StepFn<T>` is a function that:
104///
105/// - Takes a `StepContext` parameter
106/// - Returns `Result<T, Box<dyn Error + Send + Sync>>`
107/// - Can be sent between threads (`Send`)
108///
109/// # Equivalent Bounds
110///
111/// `StepFn<T>` is equivalent to:
112/// ```text
113/// FnOnce(StepContext) -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send
114/// ```
115///
116/// # Blanket Implementation
117///
118/// This trait is automatically implemented for all closures and functions
119/// that satisfy the bounds. You don't need to implement it manually.
120///
121/// # Example
122///
123/// ```rust
124/// use durable_execution_sdk::traits::StepFn;
125/// use durable_execution_sdk::handlers::StepContext;
126///
127/// // A closure that implements StepFn<i32>
128/// let step_fn = |_ctx: StepContext| -> Result<i32, Box<dyn std::error::Error + Send + Sync>> {
129///     Ok(42)
130/// };
131///
132/// // A named function that implements StepFn<String>
133/// fn process_data(ctx: StepContext) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
134///     Ok(format!("Processed by operation {}", ctx.operation_id))
135/// }
136///
137/// // Use in generic functions
138/// fn execute<T, F: StepFn<T>>(func: F) {
139///     // func can be called with a StepContext
140/// }
141///
142/// execute(step_fn);
143/// execute(process_data);
144/// ```
145pub trait StepFn<T>:
146    FnOnce(StepContext) -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send
147{
148}
149
150/// Blanket implementation for all closures meeting the bounds.
151impl<T, F> StepFn<T> for F where
152    F: FnOnce(StepContext) -> Result<T, Box<dyn std::error::Error + Send + Sync>> + Send
153{
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use serde::{Deserialize, Serialize};
160
161    // Test that common types implement DurableValue
162    #[test]
163    fn test_durable_value_primitives() {
164        fn assert_durable_value<T: DurableValue>() {}
165
166        assert_durable_value::<i32>();
167        assert_durable_value::<i64>();
168        assert_durable_value::<u32>();
169        assert_durable_value::<u64>();
170        assert_durable_value::<f32>();
171        assert_durable_value::<f64>();
172        assert_durable_value::<bool>();
173        assert_durable_value::<String>();
174        assert_durable_value::<()>();
175    }
176
177    #[test]
178    fn test_durable_value_collections() {
179        fn assert_durable_value<T: DurableValue>() {}
180
181        assert_durable_value::<Vec<i32>>();
182        assert_durable_value::<Vec<String>>();
183        assert_durable_value::<std::collections::HashMap<String, i32>>();
184        assert_durable_value::<Option<String>>();
185    }
186
187    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
188    struct TestStruct {
189        id: String,
190        value: i32,
191    }
192
193    #[test]
194    fn test_durable_value_custom_struct() {
195        fn assert_durable_value<T: DurableValue>() {}
196        assert_durable_value::<TestStruct>();
197    }
198
199    #[test]
200    fn test_durable_value_serialization() {
201        fn serialize_value<T: DurableValue>(value: &T) -> String {
202            serde_json::to_string(value).unwrap()
203        }
204
205        fn deserialize_value<T: DurableValue>(json: &str) -> T {
206            serde_json::from_str(json).unwrap()
207        }
208
209        let original = TestStruct {
210            id: "test-123".to_string(),
211            value: 42,
212        };
213
214        let json = serialize_value(&original);
215        let restored: TestStruct = deserialize_value(&json);
216
217        assert_eq!(original, restored);
218    }
219
220    #[test]
221    fn test_step_fn_closure() {
222        fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
223
224        // Test with a simple closure
225        let closure =
226            |_ctx: StepContext| -> Result<i32, Box<dyn std::error::Error + Send + Sync>> { Ok(42) };
227        assert_step_fn(closure);
228    }
229
230    #[test]
231    fn test_step_fn_with_capture() {
232        fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
233
234        let captured_value = 100;
235        let closure =
236            move |_ctx: StepContext| -> Result<i32, Box<dyn std::error::Error + Send + Sync>> {
237                Ok(captured_value)
238            };
239        assert_step_fn(closure);
240    }
241
242    #[test]
243    fn test_step_fn_returning_struct() {
244        fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
245
246        let closure =
247            |_ctx: StepContext| -> Result<TestStruct, Box<dyn std::error::Error + Send + Sync>> {
248                Ok(TestStruct {
249                    id: "test".to_string(),
250                    value: 1,
251                })
252            };
253        assert_step_fn(closure);
254    }
255
256    // Test that StepFn works with named functions
257    fn named_step_function(
258        _ctx: StepContext,
259    ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
260        Ok("result".to_string())
261    }
262
263    #[test]
264    fn test_step_fn_named_function() {
265        fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
266        assert_step_fn(named_step_function);
267    }
268
269    // Test type inference with trait aliases
270    #[test]
271    fn test_type_inference() {
272        fn process<T: DurableValue, F: StepFn<T>>(_f: F) -> &'static str {
273            "processed"
274        }
275
276        // Type inference should work without explicit type annotations
277        let result = process(|_ctx| Ok(42i32));
278        assert_eq!(result, "processed");
279
280        let result = process(|_ctx| Ok("hello".to_string()));
281        assert_eq!(result, "processed");
282    }
283
284    // Test that closures can borrow from environment (no 'static requirement)
285    #[test]
286    fn test_step_fn_borrowing_closure() {
287        fn assert_step_fn<T, F: StepFn<T>>(_f: F) {}
288
289        let external_data = "borrowed data".to_string();
290        let closure =
291            |_ctx: StepContext| -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
292                Ok(external_data.clone())
293            };
294        assert_step_fn(closure);
295    }
296}