1use serde::{Deserialize, Serialize};
36use std::fmt;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43pub enum ErrorType {
44 Unknown,
46 InvalidArgument,
48 CannotConnect,
50 Disconnected,
52 ConnectionTimeout,
54 ResponseTimeout,
56 Cancelled,
58 ResourceExhausted,
61 WorkerOverloaded,
65 Unavailable,
67 Backend(BackendError),
69}
70
71impl fmt::Display for ErrorType {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 ErrorType::Unknown => write!(f, "Unknown"),
75 ErrorType::InvalidArgument => write!(f, "InvalidArgument"),
76 ErrorType::CannotConnect => write!(f, "CannotConnect"),
77 ErrorType::Disconnected => write!(f, "Disconnected"),
78 ErrorType::ConnectionTimeout => write!(f, "ConnectionTimeout"),
79 ErrorType::ResponseTimeout => write!(f, "ResponseTimeout"),
80 ErrorType::Cancelled => write!(f, "Cancelled"),
81 ErrorType::ResourceExhausted => write!(f, "ResourceExhausted"),
82 ErrorType::WorkerOverloaded => write!(f, "WorkerOverloaded"),
83 ErrorType::Unavailable => write!(f, "Unavailable"),
84 ErrorType::Backend(sub) => write!(f, "Backend{sub}"),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95pub enum BackendError {
96 Unknown,
98 InvalidArgument,
100 CannotConnect,
102 Disconnected,
104 ConnectionTimeout,
106 ResponseTimeout,
108 Cancelled,
110 EngineShutdown,
112 StreamIncomplete,
114}
115
116impl fmt::Display for BackendError {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 match self {
119 BackendError::Unknown => write!(f, "Unknown"),
120 BackendError::InvalidArgument => write!(f, "InvalidArgument"),
121 BackendError::CannotConnect => write!(f, "CannotConnect"),
122 BackendError::Disconnected => write!(f, "Disconnected"),
123 BackendError::ConnectionTimeout => write!(f, "ConnectionTimeout"),
124 BackendError::ResponseTimeout => write!(f, "ResponseTimeout"),
125 BackendError::Cancelled => write!(f, "Cancelled"),
126 BackendError::EngineShutdown => write!(f, "EngineShutdown"),
127 BackendError::StreamIncomplete => write!(f, "StreamIncomplete"),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
154pub struct DynamoError {
155 error_type: ErrorType,
156 message: String,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 caused_by: Option<Box<DynamoError>>,
159}
160
161impl DynamoError {
162 pub fn builder() -> DynamoErrorBuilder {
164 DynamoErrorBuilder::default()
165 }
166
167 pub fn msg(message: impl Into<String>) -> Self {
169 Self::builder().message(message).build()
170 }
171
172 pub fn error_type(&self) -> ErrorType {
174 self.error_type
175 }
176
177 pub fn message(&self) -> &str {
179 &self.message
180 }
181}
182
183impl fmt::Display for DynamoError {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "{}: {}", self.error_type, self.message)
186 }
187}
188
189impl std::error::Error for DynamoError {
190 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
191 self.caused_by
192 .as_deref()
193 .map(|e| e as &(dyn std::error::Error + 'static))
194 }
195}
196
197impl<'a> From<&'a (dyn std::error::Error + 'static)> for DynamoError {
203 fn from(err: &'a (dyn std::error::Error + 'static)) -> Self {
204 if let Some(dynamo_err) = err.downcast_ref::<DynamoError>() {
205 return dynamo_err.clone();
206 }
207
208 Self {
209 error_type: ErrorType::Unknown,
210 message: err.to_string(),
211 caused_by: err.source().map(|s| Box::new(DynamoError::from(s))),
212 }
213 }
214}
215
216impl From<Box<dyn std::error::Error + 'static>> for DynamoError {
221 fn from(err: Box<dyn std::error::Error + 'static>) -> Self {
222 match err.downcast::<DynamoError>() {
223 Ok(dynamo_err) => *dynamo_err,
224 Err(err) => DynamoError::from(&*err as &(dyn std::error::Error + 'static)),
225 }
226 }
227}
228
229#[derive(Default)]
244pub struct DynamoErrorBuilder {
245 error_type: Option<ErrorType>,
246 message: Option<String>,
247 caused_by: Option<Box<DynamoError>>,
248}
249
250impl DynamoErrorBuilder {
251 pub fn error_type(mut self, error_type: ErrorType) -> Self {
253 self.error_type = Some(error_type);
254 self
255 }
256
257 pub fn message(mut self, message: impl Into<String>) -> Self {
259 self.message = Some(message.into());
260 self
261 }
262
263 pub fn cause(mut self, cause: impl std::error::Error + 'static) -> Self {
268 self.caused_by = Some(Box::new(DynamoError::from(
269 &cause as &(dyn std::error::Error + 'static),
270 )));
271 self
272 }
273
274 pub fn build(self) -> DynamoError {
278 DynamoError {
279 error_type: self.error_type.unwrap_or(ErrorType::Unknown),
280 message: self.message.unwrap_or_default(),
281 caused_by: self.caused_by,
282 }
283 }
284}
285
286pub fn match_error_chain(
298 err: &(dyn std::error::Error + 'static),
299 match_set: &[ErrorType],
300 exclude_set: &[ErrorType],
301) -> bool {
302 let mut found = false;
303 let mut current: Option<&(dyn std::error::Error + 'static)> = Some(err);
304
305 while let Some(e) = current {
306 if let Some(dynamo_err) = e.downcast_ref::<DynamoError>() {
307 if exclude_set.contains(&dynamo_err.error_type()) {
308 return false;
309 }
310 if match_set.contains(&dynamo_err.error_type()) {
311 found = true;
312 }
313 }
314 current = e.source();
315 }
316
317 found
318}
319
320#[cfg(test)]
325mod tests {
326 use super::*;
327 use std::error::Error;
328
329 const _: () = {
332 fn assert_stderror<T: std::error::Error>() {}
333 fn assert_send<T: Send>() {}
334 fn assert_sync<T: Sync>() {}
335 fn assert_static<T: 'static>() {}
336 fn assert_all() {
337 assert_stderror::<DynamoError>();
338 assert_send::<DynamoError>();
339 assert_sync::<DynamoError>();
340 assert_static::<DynamoError>();
341 }
342 };
343
344 #[test]
345 fn test_msg_constructor() {
346 let err = DynamoError::msg("something failed");
347 assert_eq!(err.error_type(), ErrorType::Unknown);
348 assert_eq!(err.message(), "something failed");
349 assert!(err.source().is_none());
350 }
351
352 #[test]
353 fn test_new_constructor_with_cause() {
354 let cause = std::io::Error::other("io error");
355 let err = DynamoError::builder()
356 .error_type(ErrorType::Unknown)
357 .message("operation failed")
358 .cause(cause)
359 .build();
360
361 assert_eq!(err.error_type(), ErrorType::Unknown);
362 assert_eq!(err.message(), "operation failed");
363 assert!(err.source().is_some());
364 }
365
366 #[test]
367 fn test_display_shows_only_current_error() {
368 let cause = std::io::Error::other("io error");
369 let err = DynamoError::builder()
370 .error_type(ErrorType::Unknown)
371 .message("operation failed")
372 .cause(cause)
373 .build();
374
375 assert_eq!(err.to_string(), "Unknown: operation failed");
377 }
378
379 #[test]
380 fn test_source_chain() {
381 let cause = std::io::Error::other("io error");
382 let err = DynamoError::builder()
383 .error_type(ErrorType::Unknown)
384 .message("operation failed")
385 .cause(cause)
386 .build();
387
388 let source = err.source().unwrap();
390 assert!(source.to_string().contains("io error"));
391 }
392
393 #[test]
394 fn test_from_boxed_std_error() {
395 let std_err = std::io::Error::other("io error");
396 let boxed: Box<dyn std::error::Error> = Box::new(std_err);
397 let dynamo_err = DynamoError::from(boxed);
398
399 assert_eq!(dynamo_err.error_type(), ErrorType::Unknown);
400 assert_eq!(dynamo_err.message(), "io error");
401 }
402
403 #[test]
404 fn test_from_boxed_takes_ownership_of_dynamo_error() {
405 let inner = DynamoError::msg("original");
406 let boxed: Box<dyn std::error::Error> = Box::new(inner);
407 let dynamo_err = DynamoError::from(boxed);
408
409 assert_eq!(dynamo_err.error_type(), ErrorType::Unknown);
411 assert_eq!(dynamo_err.message(), "original");
412 }
413
414 #[test]
415 fn test_from_boxed_with_source_chain() {
416 #[derive(Debug)]
417 struct OuterError {
418 source: std::io::Error,
419 }
420
421 impl fmt::Display for OuterError {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 write!(f, "outer error occurred")
424 }
425 }
426
427 impl std::error::Error for OuterError {
428 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
429 Some(&self.source)
430 }
431 }
432
433 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
434 let outer = OuterError { source: inner };
435 let boxed: Box<dyn std::error::Error> = Box::new(outer);
436 let dynamo_err = DynamoError::from(boxed);
437
438 assert_eq!(dynamo_err.message(), "outer error occurred");
439 assert!(dynamo_err.source().is_some());
440
441 let cause = dynamo_err.source().unwrap();
442 assert!(cause.to_string().contains("file not found"));
443 }
444
445 #[test]
446 fn test_serialization_roundtrip() {
447 let cause = DynamoError::msg("inner cause");
448 let err = DynamoError::builder()
449 .error_type(ErrorType::Unknown)
450 .message("outer error")
451 .cause(cause)
452 .build();
453
454 let json = serde_json::to_string(&err).unwrap();
455 let deserialized: DynamoError = serde_json::from_str(&json).unwrap();
456
457 assert_eq!(deserialized.error_type(), ErrorType::Unknown);
458 assert_eq!(deserialized.message(), "outer error");
459 assert!(deserialized.source().is_some());
460
461 let cause = deserialized
462 .source()
463 .unwrap()
464 .downcast_ref::<DynamoError>()
465 .unwrap();
466 assert_eq!(cause.message(), "inner cause");
467 }
468
469 #[test]
470 fn test_error_type_display() {
471 assert_eq!(ErrorType::Unknown.to_string(), "Unknown");
472 assert_eq!(ErrorType::InvalidArgument.to_string(), "InvalidArgument");
473 assert_eq!(ErrorType::CannotConnect.to_string(), "CannotConnect");
474 assert_eq!(ErrorType::Disconnected.to_string(), "Disconnected");
475 assert_eq!(
476 ErrorType::ConnectionTimeout.to_string(),
477 "ConnectionTimeout"
478 );
479 assert_eq!(ErrorType::ResponseTimeout.to_string(), "ResponseTimeout");
480 assert_eq!(ErrorType::Cancelled.to_string(), "Cancelled");
481 assert_eq!(
482 ErrorType::ResourceExhausted.to_string(),
483 "ResourceExhausted"
484 );
485 assert_eq!(ErrorType::WorkerOverloaded.to_string(), "WorkerOverloaded");
486 assert_eq!(ErrorType::Unavailable.to_string(), "Unavailable");
487 assert_eq!(
488 ErrorType::Backend(BackendError::Unknown).to_string(),
489 "BackendUnknown"
490 );
491 assert_eq!(
492 ErrorType::Backend(BackendError::InvalidArgument).to_string(),
493 "BackendInvalidArgument"
494 );
495 assert_eq!(
496 ErrorType::Backend(BackendError::CannotConnect).to_string(),
497 "BackendCannotConnect"
498 );
499 assert_eq!(
500 ErrorType::Backend(BackendError::Disconnected).to_string(),
501 "BackendDisconnected"
502 );
503 assert_eq!(
504 ErrorType::Backend(BackendError::ConnectionTimeout).to_string(),
505 "BackendConnectionTimeout"
506 );
507 assert_eq!(
508 ErrorType::Backend(BackendError::Cancelled).to_string(),
509 "BackendCancelled"
510 );
511 assert_eq!(
512 ErrorType::Backend(BackendError::EngineShutdown).to_string(),
513 "BackendEngineShutdown"
514 );
515 assert_eq!(
516 ErrorType::Backend(BackendError::StreamIncomplete).to_string(),
517 "BackendStreamIncomplete"
518 );
519 assert_eq!(
520 ErrorType::Backend(BackendError::ResponseTimeout).to_string(),
521 "BackendResponseTimeout"
522 );
523 }
524}