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}