error_forge/async_error_impl.rs
1#[cfg(feature = "async")]
2use async_trait::async_trait;
3use std::error::Error as StdError;
4
5use crate::async_error::AsyncForgeError;
6use crate::error::AppError;
7
8/// `AppError` participates in the `AsyncForgeError` surface so it can
9/// be used wherever async-aware error metadata is required.
10///
11/// All sync metadata methods (`kind`, `caption`, `is_retryable`,
12/// `is_fatal`, `status_code`, `exit_code`, `user_message`,
13/// `dev_message`) delegate to the existing
14/// [`ForgeError`](crate::error::ForgeError) implementation. The
15/// async [`async_handle`](AsyncForgeError::async_handle) method uses
16/// the trait's default no-op implementation — `AppError` has no
17/// default async behaviour beyond carrying its metadata.
18///
19/// # Breaking change from `0.9.x`
20///
21/// `0.9.x` shipped a stub `async_handle` implementation here that
22/// returned `Ok(())` regardless of input but matched on `AppError`
23/// variants as if it were doing something. The stub is removed in
24/// `1.0`; the trait now provides a no-op default and `AppError`
25/// inherits it.
26#[cfg(feature = "async")]
27#[async_trait]
28impl AsyncForgeError for AppError {
29 fn kind(&self) -> &'static str {
30 <Self as crate::error::ForgeError>::kind(self)
31 }
32
33 fn caption(&self) -> &'static str {
34 <Self as crate::error::ForgeError>::caption(self)
35 }
36
37 fn is_retryable(&self) -> bool {
38 <Self as crate::error::ForgeError>::is_retryable(self)
39 }
40
41 fn is_fatal(&self) -> bool {
42 <Self as crate::error::ForgeError>::is_fatal(self)
43 }
44
45 fn status_code(&self) -> u16 {
46 <Self as crate::error::ForgeError>::status_code(self)
47 }
48
49 fn exit_code(&self) -> i32 {
50 <Self as crate::error::ForgeError>::exit_code(self)
51 }
52
53 fn user_message(&self) -> String {
54 <Self as crate::error::ForgeError>::user_message(self)
55 }
56
57 fn dev_message(&self) -> String {
58 <Self as crate::error::ForgeError>::dev_message(self)
59 }
60
61 // `async_handle` uses the trait default (no-op `Ok(())`).
62}
63
64#[cfg(feature = "async")]
65impl AppError {
66 /// Convert an `async` operation's `Result<T, E>` (where `E:
67 /// std::error::Error + Send + Sync + 'static`) into a
68 /// `Result<T, AppError>` by wrapping the error in
69 /// `AppError::other(...)`, marked retryable and non-fatal.
70 ///
71 /// Convenience for the common `async fn -> Result<T, AppError>`
72 /// pattern where the caller wants to flatten any
73 /// `std::error::Error` into an `AppError` shape.
74 pub async fn from_async_result<T, E>(result: Result<T, E>) -> Result<T, Self>
75 where
76 E: StdError + Send + Sync + 'static,
77 {
78 match result {
79 Ok(value) => Ok(value),
80 Err(e) => Err(Self::other(e.to_string())
81 .with_fatal(false)
82 .with_retryable(true)),
83 }
84 }
85
86 /// Run the async hook on this error.
87 ///
88 /// Equivalent to calling
89 /// `<AppError as AsyncForgeError>::async_handle(&self).await`;
90 /// `AppError` does not override the trait default, so this is a
91 /// no-op `Ok(())`. Exists for symmetry with the other
92 /// `*_async` builders on `AppError`.
93 pub async fn handle_async(&self) -> Result<(), Box<dyn StdError + Send + Sync>> {
94 <Self as AsyncForgeError>::async_handle(self).await
95 }
96}