Skip to main content

qubit_dcl/double_checked/
executor_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! # Executor Error
10//!
11//! Provides executor error types for the double-checked lock executor.
12//!
13//! # Author
14//!
15//! Haixing Hu
16
17use std::error::Error;
18use std::fmt;
19
20/// Common error information for prepare lifecycle callbacks.
21///
22/// This keeps the error type name together with the message so callers can
23/// classify failures without depending on fragile string matching.
24///
25/// # Examples
26///
27/// ```rust
28/// use qubit_dcl::double_checked::CallbackError;
29///
30/// let prepare_error = CallbackError::with_type("prepare", "Resource is locked");
31/// assert_eq!(prepare_error.callback_type(), Some("prepare"));
32/// println!("prepare_error = {:?}", prepare_error);
33/// ```
34#[derive(Debug, Clone)]
35pub struct CallbackError {
36    /// Error message produced by the callback.
37    message: String,
38
39    /// Concrete type name, when available.
40    callback_type: Option<&'static str>,
41}
42
43impl CallbackError {
44    /// Builds a callback error without type metadata.
45    #[inline]
46    pub fn from_display<T: fmt::Display>(error: T) -> Self {
47        Self {
48            message: error.to_string(),
49            callback_type: None,
50        }
51    }
52
53    /// Builds a callback error with explicit callback type metadata.
54    #[inline]
55    pub fn with_type<T: fmt::Display>(source_type: &'static str, error: T) -> Self {
56        Self {
57            message: error.to_string(),
58            callback_type: Some(source_type),
59        }
60    }
61
62    /// Returns the raw message.
63    #[inline]
64    pub fn message(&self) -> &str {
65        &self.message
66    }
67
68    /// Returns the callback type label, when available.
69    #[inline]
70    pub fn callback_type(&self) -> Option<&'static str> {
71        self.callback_type
72    }
73
74    /// Returns whether the callback type label is set.
75    #[inline]
76    pub fn is_typed(&self) -> bool {
77        self.callback_type.is_some()
78    }
79}
80
81impl fmt::Display for CallbackError {
82    #[inline]
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self.callback_type {
85            Some(callback_type) => write!(f, "{}: {}", callback_type, self.message),
86            None => write!(f, "{}", self.message),
87        }
88    }
89}
90
91/// Executor error types.
92///
93/// # Type Parameters
94///
95/// * `E` - The original error type from task execution
96///
97/// # Examples
98///
99/// ```rust
100/// use qubit_dcl::double_checked::ExecutorError;
101/// use qubit_dcl::double_checked::CallbackError;
102///
103/// let error: ExecutorError<String> =
104///     ExecutorError::TaskFailed("task failed".to_string());
105/// println!("Error: {}", error);
106///
107/// let error_with_msg: ExecutorError<String> =
108///     ExecutorError::PrepareFailed(CallbackError::from_display("Service is not running"));
109/// println!("Error: {}", error_with_msg);
110/// ```
111///
112/// # Author
113///
114/// Haixing Hu
115///
116#[derive(Debug)]
117pub enum ExecutorError<E> {
118    /// Task execution failed with original error
119    TaskFailed(E),
120
121    /// Task execution panicked.
122    Panic(CallbackError),
123
124    /// Preparation action failed
125    PrepareFailed(CallbackError),
126
127    /// Commit action for a successfully completed prepare action failed.
128    PrepareCommitFailed(CallbackError),
129
130    /// Rollback action for a successfully completed prepare action failed.
131    PrepareRollbackFailed {
132        /// The original error that triggered the rollback
133        original: CallbackError,
134        /// The error that occurred during prepare rollback
135        rollback: CallbackError,
136    },
137}
138
139impl<E> fmt::Display for ExecutorError<E>
140where
141    E: fmt::Display,
142{
143    /// Formats this executor error for user-facing diagnostics.
144    ///
145    /// # Parameters
146    ///
147    /// * `f` - Formatter receiving the human-readable error text.
148    ///
149    /// # Returns
150    ///
151    /// [`fmt::Result`] from writing the formatted error text.
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match self {
154            ExecutorError::TaskFailed(e) => {
155                write!(f, "Task execution failed: {}", e)
156            }
157            ExecutorError::Panic(error) => {
158                write!(f, "Execution panicked: {}", error)
159            }
160            ExecutorError::PrepareFailed(msg) => {
161                write!(f, "Preparation action failed: {}", msg)
162            }
163            ExecutorError::PrepareCommitFailed(msg) => {
164                write!(f, "Prepare commit action failed: {}", msg)
165            }
166            ExecutorError::PrepareRollbackFailed { original, rollback } => {
167                write!(
168                    f,
169                    "Prepare rollback failed: original error = {}, rollback error = {}",
170                    original, rollback
171                )
172            }
173        }
174    }
175}
176
177impl<E> ExecutorError<E> {
178    /// Returns the callback type label, when the error comes from a callback and
179    /// the type is available.
180    ///
181    /// This returns `None` for task failures and callback errors without
182    /// associated type labels.
183    #[inline]
184    pub fn callback_type(&self) -> Option<&'static str> {
185        match self {
186            ExecutorError::TaskFailed(_) => None,
187            ExecutorError::Panic(error) => error.callback_type(),
188            ExecutorError::PrepareFailed(error) => error.callback_type(),
189            ExecutorError::PrepareCommitFailed(error) => error.callback_type(),
190            ExecutorError::PrepareRollbackFailed { original, rollback } => {
191                rollback.callback_type().or(original.callback_type())
192            }
193        }
194    }
195}
196
197impl<E> Error for ExecutorError<E>
198where
199    E: Error + 'static,
200{
201    /// Returns the underlying task error as the standard error source.
202    ///
203    /// Prepare lifecycle failures store their messages as strings and therefore
204    /// do not expose a structured source error.
205    fn source(&self) -> Option<&(dyn Error + 'static)> {
206        match self {
207            ExecutorError::TaskFailed(error) => Some(error),
208            ExecutorError::Panic(_)
209            | ExecutorError::PrepareFailed(_)
210            | ExecutorError::PrepareCommitFailed(_)
211            | ExecutorError::PrepareRollbackFailed { .. } => None,
212        }
213    }
214}