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    #[inline]
105    pub fn callback_type(&self) -> Option<&'static str> {
106        match self {
107            ExecutorError::TaskFailed(_) => None,
108            ExecutorError::Panic(error) => error.callback_type(),
109            ExecutorError::PrepareFailed(error) => error.callback_type(),
110            ExecutorError::PrepareCommitFailed(error) => error.callback_type(),
111            ExecutorError::PrepareRollbackFailed { original, rollback } => {
112                rollback.callback_type().or(original.callback_type())
113            }
114        }
115    }
116}
117
118impl<E> Error for ExecutorError<E>
119where
120    E: Error + 'static,
121{
122    /// Returns the underlying task error as the standard error source.
123    ///
124    /// Prepare lifecycle failures store their messages as strings and therefore
125    /// do not expose a structured source error.
126    fn source(&self) -> Option<&(dyn Error + 'static)> {
127        match self {
128            ExecutorError::TaskFailed(error) => Some(error),
129            ExecutorError::Panic(_) => None,
130            ExecutorError::PrepareFailed(_) => None,
131            ExecutorError::PrepareCommitFailed(_) => None,
132            ExecutorError::PrepareRollbackFailed { .. } => None,
133        }
134    }
135}