Skip to main content

durable_execution_sdk_testing/
error.rs

1//! Error types for the testing utilities crate.
2//!
3//! This module defines the error types used throughout the testing framework,
4//! wrapping SDK errors and adding testing-specific error variants.
5
6use durable_execution_sdk::{DurableError, OperationType};
7use thiserror::Error;
8
9use crate::types::WaitingOperationStatus;
10
11/// Errors that can occur during testing.
12///
13/// This enum covers all possible error conditions that can occur
14/// when using the testing utilities.
15///
16/// # Examples
17///
18/// ```
19/// use durable_execution_sdk_testing::TestError;
20///
21/// // Operation not found
22/// let err = TestError::OperationNotFound("my-step".to_string());
23/// assert!(err.to_string().contains("my-step"));
24///
25/// // Type mismatch
26/// let err = TestError::OperationTypeMismatch {
27///     expected: durable_execution_sdk::OperationType::Step,
28///     found: durable_execution_sdk::OperationType::Wait,
29/// };
30/// assert!(err.to_string().contains("Step"));
31/// ```
32#[derive(Debug, Error)]
33pub enum TestError {
34    /// The handler execution failed.
35    #[error("Handler execution failed: {0}")]
36    ExecutionFailed(#[from] DurableError),
37
38    /// Operation not found.
39    #[error("Operation not found: {0}")]
40    OperationNotFound(String),
41
42    /// Wrong operation type for the requested details.
43    #[error("Operation type mismatch: expected {expected}, found {found}")]
44    OperationTypeMismatch {
45        /// The expected operation type
46        expected: OperationType,
47        /// The actual operation type found
48        found: OperationType,
49    },
50
51    /// Callback operation required but not found.
52    #[error("Not a callback operation")]
53    NotCallbackOperation,
54
55    /// Function not registered for invoke.
56    #[error("Function not registered: {0}")]
57    FunctionNotRegistered(String),
58
59    /// Timeout waiting for operation.
60    #[error("Timeout waiting for operation: {0}")]
61    WaitTimeout(String),
62
63    /// Execution completed before operation reached expected state.
64    #[error("Execution completed before operation {0} reached state {1}")]
65    ExecutionCompletedEarly(String, WaitingOperationStatus),
66
67    /// Serialization/deserialization error.
68    #[error("Serialization error: {0}")]
69    SerializationError(#[from] serde_json::Error),
70
71    /// AWS SDK error.
72    #[error("AWS SDK error: {0}")]
73    AwsError(String),
74
75    /// Test environment not set up.
76    #[error("Test environment not set up. Call setup_test_environment() first.")]
77    EnvironmentNotSetUp,
78
79    /// Invalid configuration.
80    #[error("Invalid configuration: {0}")]
81    InvalidConfiguration(String),
82
83    /// Result not available (execution not complete or failed).
84    #[error("Result not available: {0}")]
85    ResultNotAvailable(String),
86
87    /// Invalid checkpoint token.
88    #[error("Invalid checkpoint token: {0}")]
89    InvalidCheckpointToken(String),
90
91    /// Checkpoint server error.
92    #[error("Checkpoint server error: {0}")]
93    CheckpointServerError(String),
94
95    /// Checkpoint communication error.
96    #[error("Checkpoint communication error: {0}")]
97    CheckpointCommunicationError(String),
98
99    /// Execution not found.
100    #[error("Execution not found: {0}")]
101    ExecutionNotFound(String),
102
103    /// Invocation not found.
104    #[error("Invocation not found: {0}")]
105    InvocationNotFound(String),
106
107    /// Callback already completed.
108    #[error("Callback already completed: {0}")]
109    CallbackAlreadyCompleted(String),
110
111    /// Callback not found.
112    #[error("Callback not found: {0}")]
113    CallbackNotFound(String),
114}
115
116impl TestError {
117    /// Creates a new OperationNotFound error.
118    pub fn operation_not_found(name: impl Into<String>) -> Self {
119        Self::OperationNotFound(name.into())
120    }
121
122    /// Creates a new OperationTypeMismatch error.
123    pub fn type_mismatch(expected: OperationType, found: OperationType) -> Self {
124        Self::OperationTypeMismatch { expected, found }
125    }
126
127    /// Creates a new FunctionNotRegistered error.
128    pub fn function_not_registered(name: impl Into<String>) -> Self {
129        Self::FunctionNotRegistered(name.into())
130    }
131
132    /// Creates a new WaitTimeout error.
133    pub fn wait_timeout(operation: impl Into<String>) -> Self {
134        Self::WaitTimeout(operation.into())
135    }
136
137    /// Creates a new ExecutionCompletedEarly error.
138    pub fn execution_completed_early(
139        operation: impl Into<String>,
140        status: WaitingOperationStatus,
141    ) -> Self {
142        Self::ExecutionCompletedEarly(operation.into(), status)
143    }
144
145    /// Creates a new AwsError.
146    pub fn aws_error(message: impl Into<String>) -> Self {
147        Self::AwsError(message.into())
148    }
149
150    /// Creates a new InvalidConfiguration error.
151    pub fn invalid_configuration(message: impl Into<String>) -> Self {
152        Self::InvalidConfiguration(message.into())
153    }
154
155    /// Creates a new ResultNotAvailable error.
156    pub fn result_not_available(message: impl Into<String>) -> Self {
157        Self::ResultNotAvailable(message.into())
158    }
159
160    /// Creates a new InvalidCheckpointToken error.
161    pub fn invalid_checkpoint_token(message: impl Into<String>) -> Self {
162        Self::InvalidCheckpointToken(message.into())
163    }
164
165    /// Creates a new CheckpointServerError.
166    pub fn checkpoint_server_error(message: impl Into<String>) -> Self {
167        Self::CheckpointServerError(message.into())
168    }
169
170    /// Creates a new CheckpointCommunicationError.
171    pub fn checkpoint_communication_error(message: impl Into<String>) -> Self {
172        Self::CheckpointCommunicationError(message.into())
173    }
174
175    /// Creates a new ExecutionNotFound error.
176    pub fn execution_not_found(message: impl Into<String>) -> Self {
177        Self::ExecutionNotFound(message.into())
178    }
179
180    /// Creates a new InvocationNotFound error.
181    pub fn invocation_not_found(message: impl Into<String>) -> Self {
182        Self::InvocationNotFound(message.into())
183    }
184
185    /// Creates a new CallbackAlreadyCompleted error.
186    pub fn callback_already_completed(message: impl Into<String>) -> Self {
187        Self::CallbackAlreadyCompleted(message.into())
188    }
189
190    /// Creates a new CallbackNotFound error.
191    pub fn callback_not_found(message: impl Into<String>) -> Self {
192        Self::CallbackNotFound(message.into())
193    }
194
195    /// Returns true if this error is retriable.
196    pub fn is_retriable(&self) -> bool {
197        match self {
198            Self::ExecutionFailed(e) => e.is_retriable(),
199            Self::WaitTimeout(_) => true,
200            _ => false,
201        }
202    }
203}
204
205/// Result type for testing operations.
206pub type TestResult<T> = Result<T, TestError>;
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn test_operation_not_found() {
214        let err = TestError::operation_not_found("my-step");
215        assert!(matches!(err, TestError::OperationNotFound(_)));
216        assert!(err.to_string().contains("my-step"));
217    }
218
219    #[test]
220    fn test_type_mismatch() {
221        let err = TestError::type_mismatch(OperationType::Step, OperationType::Wait);
222        assert!(matches!(err, TestError::OperationTypeMismatch { .. }));
223        assert!(err.to_string().contains("Step"));
224        assert!(err.to_string().contains("Wait"));
225    }
226
227    #[test]
228    fn test_not_callback_operation() {
229        let err = TestError::NotCallbackOperation;
230        assert!(err.to_string().contains("callback"));
231    }
232
233    #[test]
234    fn test_function_not_registered() {
235        let err = TestError::function_not_registered("my-function");
236        assert!(matches!(err, TestError::FunctionNotRegistered(_)));
237        assert!(err.to_string().contains("my-function"));
238    }
239
240    #[test]
241    fn test_wait_timeout() {
242        let err = TestError::wait_timeout("my-operation");
243        assert!(matches!(err, TestError::WaitTimeout(_)));
244        assert!(err.to_string().contains("my-operation"));
245    }
246
247    #[test]
248    fn test_execution_completed_early() {
249        let err =
250            TestError::execution_completed_early("my-operation", WaitingOperationStatus::Completed);
251        assert!(matches!(err, TestError::ExecutionCompletedEarly(_, _)));
252        assert!(err.to_string().contains("my-operation"));
253        assert!(err.to_string().contains("Completed"));
254    }
255
256    #[test]
257    fn test_aws_error() {
258        let err = TestError::aws_error("Connection failed");
259        assert!(matches!(err, TestError::AwsError(_)));
260        assert!(err.to_string().contains("Connection failed"));
261    }
262
263    #[test]
264    fn test_environment_not_set_up() {
265        let err = TestError::EnvironmentNotSetUp;
266        assert!(err.to_string().contains("setup_test_environment"));
267    }
268
269    #[test]
270    fn test_from_durable_error() {
271        let durable_err = DurableError::validation("Invalid input");
272        let test_err: TestError = durable_err.into();
273        assert!(matches!(test_err, TestError::ExecutionFailed(_)));
274    }
275
276    #[test]
277    fn test_from_serde_error() {
278        let json_err = serde_json::from_str::<String>("invalid").unwrap_err();
279        let test_err: TestError = json_err.into();
280        assert!(matches!(test_err, TestError::SerializationError(_)));
281    }
282
283    #[test]
284    fn test_is_retriable() {
285        let timeout_err = TestError::wait_timeout("op");
286        assert!(timeout_err.is_retriable());
287
288        let not_found_err = TestError::operation_not_found("op");
289        assert!(!not_found_err.is_retriable());
290
291        let retriable_durable =
292            TestError::ExecutionFailed(DurableError::checkpoint_retriable("temp error"));
293        assert!(retriable_durable.is_retriable());
294
295        let non_retriable_durable =
296            TestError::ExecutionFailed(DurableError::validation("bad input"));
297        assert!(!non_retriable_durable.is_retriable());
298    }
299}