Skip to main content

qubit_event_bus/core/
event_bus_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Error type returned by event bus operations.
11
12use std::error::Error;
13use std::fmt::{
14    self,
15    Display,
16    Formatter,
17};
18use std::time::Duration;
19
20/// Result type used by event bus operations.
21pub type EventBusResult<T> = Result<T, EventBusError>;
22
23/// Error returned by event bus configuration, publishing, or subscription work.
24#[derive(Debug, Clone, Eq, PartialEq)]
25pub enum EventBusError {
26    /// Operation requires a started event bus.
27    NotStarted,
28    /// The event bus could not be started.
29    StartFailed {
30        /// Human-readable startup failure reason.
31        message: String,
32    },
33    /// An argument value is invalid.
34    InvalidArgument {
35        /// Argument name.
36        field: &'static str,
37        /// Human-readable validation message.
38        message: String,
39    },
40    /// A required builder field is missing.
41    MissingField {
42        /// Missing field name.
43        field: &'static str,
44    },
45    /// Subscriber handler failed.
46    HandlerFailed {
47        /// Human-readable failure message.
48        message: String,
49    },
50    /// Subscriber handler or interceptor panicked.
51    HandlerPanicked,
52    /// A configured interceptor failed while processing an event.
53    InterceptorFailed {
54        /// Interceptor phase.
55        phase: &'static str,
56        /// Human-readable failure message.
57        message: String,
58    },
59    /// A configured error handler failed while processing another error.
60    ErrorHandlerFailed {
61        /// Error handling phase.
62        phase: &'static str,
63        /// Human-readable failure message.
64        message: String,
65    },
66    /// Dead-letter routing failed after a terminal subscriber failure.
67    DeadLetterFailed {
68        /// Human-readable routing failure message.
69        message: String,
70    },
71    /// Managed executor rejected event processing work.
72    ExecutionRejected {
73        /// Human-readable rejection reason.
74        message: String,
75    },
76    /// Graceful shutdown did not complete before the configured timeout.
77    ShutdownTimedOut {
78        /// Timeout used for the shutdown wait.
79        timeout: Duration,
80    },
81    /// Shared state lock was poisoned.
82    LockPoisoned {
83        /// Shared resource name.
84        resource: &'static str,
85    },
86    /// A type-erased event or handler had an unexpected payload type.
87    TypeMismatch {
88        /// Expected Rust type name.
89        expected: &'static str,
90        /// Actual Rust type name.
91        actual: &'static str,
92    },
93    /// Operation is not supported by this backend.
94    UnsupportedOperation {
95        /// Operation name or feature category.
96        operation: &'static str,
97    },
98}
99
100impl EventBusError {
101    /// Creates [`EventBusError::NotStarted`].
102    ///
103    /// # Returns
104    /// Error indicating that the bus must be started first.
105    pub const fn not_started() -> Self {
106        Self::NotStarted
107    }
108
109    /// Creates [`EventBusError::StartFailed`].
110    ///
111    /// # Parameters
112    /// - `message`: Startup failure details.
113    ///
114    /// # Returns
115    /// Startup error with context.
116    pub fn start_failed(message: impl Into<String>) -> Self {
117        Self::StartFailed {
118            message: message.into(),
119        }
120    }
121
122    /// Creates [`EventBusError::InvalidArgument`].
123    ///
124    /// # Parameters
125    /// - `field`: Argument name.
126    /// - `message`: Validation message.
127    ///
128    /// # Returns
129    /// Validation error with field context.
130    pub fn invalid_argument(field: &'static str, message: impl Into<String>) -> Self {
131        Self::InvalidArgument {
132            field,
133            message: message.into(),
134        }
135    }
136
137    /// Creates [`EventBusError::MissingField`].
138    ///
139    /// # Parameters
140    /// - `field`: Missing builder field.
141    ///
142    /// # Returns
143    /// Builder validation error.
144    pub const fn missing_field(field: &'static str) -> Self {
145        Self::MissingField { field }
146    }
147
148    /// Creates [`EventBusError::HandlerFailed`].
149    ///
150    /// # Parameters
151    /// - `message`: Handler failure description.
152    ///
153    /// # Returns
154    /// Handler failure error.
155    pub fn handler_failed(message: impl Into<String>) -> Self {
156        Self::HandlerFailed {
157            message: message.into(),
158        }
159    }
160
161    /// Creates [`EventBusError::HandlerPanicked`].
162    ///
163    /// # Returns
164    /// Handler panic error used by subscriber failure handling.
165    pub const fn handler_panicked() -> Self {
166        Self::HandlerPanicked
167    }
168
169    /// Creates [`EventBusError::InterceptorFailed`].
170    ///
171    /// # Parameters
172    /// - `phase`: Interceptor phase.
173    /// - `message`: Interceptor failure details.
174    ///
175    /// # Returns
176    /// Interceptor failure with context.
177    pub fn interceptor_failed(phase: &'static str, message: impl Into<String>) -> Self {
178        Self::InterceptorFailed {
179            phase,
180            message: message.into(),
181        }
182    }
183
184    /// Creates [`EventBusError::ErrorHandlerFailed`].
185    ///
186    /// # Parameters
187    /// - `phase`: Error handling phase.
188    /// - `message`: Handler failure details.
189    ///
190    /// # Returns
191    /// Error handler failure with context.
192    pub fn error_handler_failed(phase: &'static str, message: impl Into<String>) -> Self {
193        Self::ErrorHandlerFailed {
194            phase,
195            message: message.into(),
196        }
197    }
198
199    /// Creates [`EventBusError::DeadLetterFailed`].
200    ///
201    /// # Parameters
202    /// - `message`: Dead-letter routing failure details.
203    ///
204    /// # Returns
205    /// Dead-letter failure with context.
206    pub fn dead_letter_failed(message: impl Into<String>) -> Self {
207        Self::DeadLetterFailed {
208            message: message.into(),
209        }
210    }
211
212    /// Creates [`EventBusError::ExecutionRejected`].
213    ///
214    /// # Parameters
215    /// - `message`: Executor rejection details.
216    ///
217    /// # Returns
218    /// Rejection error with executor context.
219    pub fn execution_rejected(message: impl Into<String>) -> Self {
220        Self::ExecutionRejected {
221            message: message.into(),
222        }
223    }
224
225    /// Creates [`EventBusError::ShutdownTimedOut`].
226    ///
227    /// # Parameters
228    /// - `timeout`: Timeout that elapsed before shutdown completed.
229    ///
230    /// # Returns
231    /// Shutdown timeout error with the configured duration.
232    pub const fn shutdown_timed_out(timeout: Duration) -> Self {
233        Self::ShutdownTimedOut { timeout }
234    }
235
236    /// Creates [`EventBusError::LockPoisoned`].
237    ///
238    /// # Parameters
239    /// - `resource`: Name of the poisoned shared state.
240    ///
241    /// # Returns
242    /// Lock-poisoning error.
243    pub const fn lock_poisoned(resource: &'static str) -> Self {
244        Self::LockPoisoned { resource }
245    }
246
247    /// Creates [`EventBusError::TypeMismatch`].
248    ///
249    /// # Parameters
250    /// - `expected`: Expected type name.
251    /// - `actual`: Actual type name.
252    ///
253    /// # Returns
254    /// Type-erasure mismatch error.
255    pub const fn type_mismatch(expected: &'static str, actual: &'static str) -> Self {
256        Self::TypeMismatch { expected, actual }
257    }
258
259    /// Creates [`EventBusError::UnsupportedOperation`].
260    ///
261    /// # Parameters
262    /// - `operation`: Operation name or feature category.
263    ///
264    /// # Returns
265    /// Error indicating that the current backend does not support the operation.
266    pub const fn unsupported_operation(operation: &'static str) -> Self {
267        Self::UnsupportedOperation { operation }
268    }
269
270    /// Returns a stable symbolic error kind.
271    ///
272    /// # Returns
273    /// Static string useful for diagnostics and dead-letter metadata.
274    pub const fn kind(&self) -> &'static str {
275        match self {
276            Self::NotStarted => "not_started",
277            Self::StartFailed { .. } => "start_failed",
278            Self::InvalidArgument { .. } => "invalid_argument",
279            Self::MissingField { .. } => "missing_field",
280            Self::HandlerFailed { .. } => "handler_failed",
281            Self::HandlerPanicked => "handler_panicked",
282            Self::InterceptorFailed { .. } => "interceptor_failed",
283            Self::ErrorHandlerFailed { .. } => "error_handler_failed",
284            Self::DeadLetterFailed { .. } => "dead_letter_failed",
285            Self::ExecutionRejected { .. } => "execution_rejected",
286            Self::ShutdownTimedOut { .. } => "shutdown_timed_out",
287            Self::LockPoisoned { .. } => "lock_poisoned",
288            Self::TypeMismatch { .. } => "type_mismatch",
289            Self::UnsupportedOperation { .. } => "unsupported_operation",
290        }
291    }
292}
293
294impl Display for EventBusError {
295    /// Formats the error for logs and assertions.
296    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
297        match self {
298            Self::NotStarted => write!(formatter, "the EventBus has not been started"),
299            Self::StartFailed { message } => {
300                write!(formatter, "failed to start EventBus: {message}")
301            }
302            Self::InvalidArgument { field, message } => {
303                write!(formatter, "invalid argument `{field}`: {message}")
304            }
305            Self::MissingField { field } => write!(formatter, "missing required field `{field}`"),
306            Self::HandlerFailed { message } => write!(formatter, "event handler failed: {message}"),
307            Self::HandlerPanicked => write!(formatter, "event handler panicked"),
308            Self::InterceptorFailed { phase, message } => {
309                write!(formatter, "{phase} interceptor failed: {message}")
310            }
311            Self::ErrorHandlerFailed { phase, message } => {
312                write!(formatter, "{phase} error handler failed: {message}")
313            }
314            Self::DeadLetterFailed { message } => {
315                write!(formatter, "dead-letter routing failed: {message}")
316            }
317            Self::ExecutionRejected { message } => {
318                write!(formatter, "event processing task was rejected: {message}")
319            }
320            Self::ShutdownTimedOut { timeout } => {
321                write!(formatter, "event bus shutdown timed out after {timeout:?}")
322            }
323            Self::LockPoisoned { resource } => {
324                write!(formatter, "shared state lock was poisoned: {resource}")
325            }
326            Self::TypeMismatch { expected, actual } => {
327                write!(
328                    formatter,
329                    "event payload type mismatch: expected {expected}, got {actual}"
330                )
331            }
332            Self::UnsupportedOperation { operation } => {
333                write!(formatter, "unsupported event bus operation: {operation}")
334            }
335        }
336    }
337}
338
339impl Error for EventBusError {}