Skip to main content

qubit_dcl/double_checked/
executor_error.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # Executor Error
11//!
12//! Provides executor error types for the double-checked lock executor.
13//!
14
15use std::error::Error;
16use std::fmt;
17
18use super::CallbackError;
19
20/// Executor error types.
21///
22/// # Type Parameters
23///
24/// * `E` - The original error type from task execution
25///
26/// # Examples
27///
28/// ```rust
29/// use qubit_dcl::double_checked::ExecutorError;
30/// use qubit_dcl::double_checked::CallbackError;
31///
32/// let error: ExecutorError<String> =
33///     ExecutorError::TaskFailed("task failed".to_string());
34/// println!("Error: {}", error);
35///
36/// let error_with_msg: ExecutorError<String> =
37///     ExecutorError::PrepareFailed(CallbackError::from_display("Service is not running"));
38/// println!("Error: {}", error_with_msg);
39/// ```
40///
41///
42#[derive(Debug)]
43pub enum ExecutorError<E> {
44    /// Task execution failed with original error
45    TaskFailed(E),
46
47    /// Task execution panicked.
48    Panic(CallbackError),
49
50    /// Preparation action failed
51    PrepareFailed(CallbackError),
52
53    /// Commit action for a successfully completed prepare action failed.
54    PrepareCommitFailed(CallbackError),
55
56    /// Rollback action for a successfully completed prepare action failed.
57    PrepareRollbackFailed {
58        /// The original error that triggered the rollback
59        original: CallbackError,
60        /// The error that occurred during prepare rollback
61        rollback: CallbackError,
62    },
63}
64
65impl<E> fmt::Display for ExecutorError<E>
66where
67    E: fmt::Display,
68{
69    /// Formats this executor error for user-facing diagnostics.
70    ///
71    /// # Parameters
72    ///
73    /// * `f` - Formatter receiving the human-readable error text.
74    ///
75    /// # Returns
76    ///
77    /// [`fmt::Result`] from writing the formatted error text.
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            ExecutorError::TaskFailed(e) => {
81                write!(f, "Task execution failed: {}", e)
82            }
83            ExecutorError::Panic(error) => {
84                write!(f, "Execution panicked: {}", error)
85            }
86            ExecutorError::PrepareFailed(msg) => {
87                write!(f, "Preparation action failed: {}", msg)
88            }
89            ExecutorError::PrepareCommitFailed(msg) => {
90                write!(f, "Prepare commit action failed: {}", msg)
91            }
92            #[rustfmt::skip]
93            ExecutorError::PrepareRollbackFailed { original, rollback } => write!(f, "Prepare rollback failed: original error = {original}, rollback error = {rollback}"),
94        }
95    }
96}
97
98impl<E> ExecutorError<E> {
99    /// Returns the callback type label, when the error comes from a callback and
100    /// the type is available.
101    ///
102    /// This returns `None` for task failures and callback errors without
103    /// associated type labels.
104    ///
105    /// # Returns
106    ///
107    /// `Some(label)` for callback failures that carry callback type metadata,
108    /// or `None` for task failures and untyped callback failures.
109    #[inline]
110    pub fn callback_type(&self) -> Option<&'static str> {
111        match self {
112            ExecutorError::TaskFailed(_) => None,
113            ExecutorError::Panic(error) => error.callback_type(),
114            ExecutorError::PrepareFailed(error) => error.callback_type(),
115            ExecutorError::PrepareCommitFailed(error) => error.callback_type(),
116            ExecutorError::PrepareRollbackFailed { original, rollback } => {
117                rollback.callback_type().or(original.callback_type())
118            }
119        }
120    }
121}
122
123impl<E> Error for ExecutorError<E>
124where
125    E: Error + 'static,
126{
127    /// Returns the underlying task error as the standard error source.
128    ///
129    /// Prepare lifecycle failures store their messages as strings and therefore
130    /// do not expose a structured source error.
131    ///
132    /// # Returns
133    ///
134    /// `Some(error)` for [`ExecutorError::TaskFailed`], or `None` for panic and
135    /// prepare lifecycle failures.
136    fn source(&self) -> Option<&(dyn Error + 'static)> {
137        match self {
138            ExecutorError::TaskFailed(error) => Some(error),
139            ExecutorError::Panic(_) => None,
140            ExecutorError::PrepareFailed(_) => None,
141            ExecutorError::PrepareCommitFailed(_) => None,
142            ExecutorError::PrepareRollbackFailed { .. } => None,
143        }
144    }
145}