Skip to main content

error_forge/
async_error.rs

1use std::backtrace::Backtrace;
2use std::error::Error as StdError;
3
4#[cfg(feature = "async")]
5use async_trait::async_trait;
6
7/// Async-aware companion trait to [`ForgeError`](crate::error::ForgeError).
8///
9/// Carries the same sync metadata methods as `ForgeError` plus a single
10/// async hook, [`async_handle`](Self::async_handle), that callers can
11/// override to run an `await` step (logging, telemetry, cleanup) when
12/// an error surfaces in an async context. The default implementation
13/// is a no-op — implementors who do not need an async hook can ignore
14/// the method entirely.
15///
16/// # Example
17///
18/// Requires the `async` cargo feature (which pulls in `async-trait`).
19/// The hidden `#[cfg(feature = "async")]` gate means this doctest is
20/// only compiled when the feature is enabled — under
21/// `cargo test --all-features` it runs; otherwise it is silently
22/// skipped.
23///
24/// ```
25/// # #[cfg(feature = "async")] {
26/// use error_forge::async_error::AsyncForgeError;
27/// use async_trait::async_trait;
28/// use std::error::Error as StdError;
29///
30/// #[derive(Debug)]
31/// struct MyAsyncError { message: String }
32///
33/// impl std::fmt::Display for MyAsyncError {
34///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35///         write!(f, "{}", self.message)
36///     }
37/// }
38///
39/// impl std::error::Error for MyAsyncError {}
40///
41/// #[async_trait]
42/// impl AsyncForgeError for MyAsyncError {
43///     fn kind(&self) -> &'static str { "AsyncExample" }
44///     fn caption(&self) -> &'static str { "Async Example Error" }
45///
46///     // Override the no-op default if you want async behaviour:
47///     async fn async_handle(&self) -> Result<(), Box<dyn StdError + Send + Sync>> {
48///         // Telemetry, cleanup, etc. would go here.
49///         Ok(())
50///     }
51/// }
52/// # }
53/// ```
54///
55/// # Breaking change from `0.9.x`
56///
57/// In `0.9.x`, [`async_handle`](Self::async_handle) was a required
58/// method with no default body, and the [`AppError`](crate::error::AppError)
59/// implementation provided a stub that did nothing. In `1.0`,
60/// [`async_handle`](Self::async_handle) gains a default no-op body
61/// and the stub `AppError` implementation is removed. Implementors
62/// who actually want async behaviour override the default; everyone
63/// else can derive the trait without writing the method.
64#[cfg(feature = "async")]
65#[async_trait]
66pub trait AsyncForgeError: StdError + Send + Sync + 'static {
67    /// Returns the kind of error, typically matching the enum variant.
68    fn kind(&self) -> &'static str;
69
70    /// Returns a human-readable caption for the error.
71    fn caption(&self) -> &'static str;
72
73    /// Returns true if the operation can be retried.
74    fn is_retryable(&self) -> bool {
75        false
76    }
77
78    /// Returns true if the error is fatal and should terminate the program.
79    fn is_fatal(&self) -> bool {
80        false
81    }
82
83    /// Returns an appropriate HTTP status code for the error.
84    fn status_code(&self) -> u16 {
85        500
86    }
87
88    /// Returns an appropriate process exit code for the error.
89    fn exit_code(&self) -> i32 {
90        1
91    }
92
93    /// Returns a user-facing message that can be shown to end users.
94    fn user_message(&self) -> String {
95        self.to_string()
96    }
97
98    /// Returns a detailed technical message for developers/logs.
99    fn dev_message(&self) -> String {
100        format!("[{}] {}", self.kind(), self)
101    }
102
103    /// Returns a backtrace if available.
104    fn backtrace(&self) -> Option<&Backtrace> {
105        None
106    }
107
108    /// Async hook called when the implementor wants to run an
109    /// `await` step (logging, cleanup, telemetry) on error surfacing.
110    /// The default is a no-op; override if you need behaviour.
111    async fn async_handle(&self) -> Result<(), Box<dyn StdError + Send + Sync>> {
112        Ok(())
113    }
114
115    /// Registers the error with the central error hook (if any).
116    fn register(&self) {
117        crate::macros::call_error_hook(
118            self.caption(),
119            self.kind(),
120            self.is_fatal(),
121            self.is_retryable(),
122        );
123    }
124}
125
126/// Type alias for async error-forge results.
127#[cfg(feature = "async")]
128pub type AsyncResult<T, E> = std::result::Result<T, E>;