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