Skip to main content

qubit_retry/error/
attempt_failure.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//! Attempt-level failure values.
11//!
12//! A retry failure describes why one operation attempt did not produce a
13//! successful result. It is distinct from [`crate::RetryError`], which describes
14//! why the whole retry flow stopped.
15
16use std::fmt;
17
18use serde::{
19    Deserialize,
20    Serialize,
21};
22
23use super::attempt_executor_error::AttemptExecutorError;
24use super::attempt_panic::AttemptPanic;
25
26/// Failure produced by a single operation attempt.
27///
28/// The generic parameter `E` is the caller's operation error type. Timeout,
29/// panic, and executor failures do not contain `E` because they are generated
30/// by the retry runtime, not returned by the operation.
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(bound(
33    serialize = "E: serde::Serialize",
34    deserialize = "E: serde::de::DeserializeOwned"
35))]
36pub enum AttemptFailure<E> {
37    /// The operation returned an application error.
38    Error(E),
39
40    /// The attempt exceeded the effective timeout.
41    ///
42    /// This can be the configured per-attempt timeout, the remaining
43    /// max-operation-elapsed budget, or the remaining max-total-elapsed budget
44    /// used by async and worker-thread attempts.
45    Timeout,
46
47    /// The attempt panicked inside an isolated execution boundary.
48    Panic(AttemptPanic),
49
50    /// The retry executor failed before the attempt could run normally.
51    Executor(AttemptExecutorError),
52}
53
54impl<E> AttemptFailure<E> {
55    /// Returns the application error when this failure wraps one.
56    ///
57    /// # Parameters
58    /// This method has no parameters.
59    ///
60    /// # Returns
61    /// `Some(&E)` for [`AttemptFailure::Error`], or `None` for
62    /// runtime-generated failures.
63    ///
64    /// # Errors
65    /// This method does not return errors.
66    #[inline]
67    pub fn as_error(&self) -> Option<&E> {
68        match self {
69            Self::Error(error) => Some(error),
70            Self::Timeout | Self::Panic(_) | Self::Executor(_) => None,
71        }
72    }
73
74    /// Consumes the failure and returns the application error when present.
75    ///
76    /// # Parameters
77    /// This method has no parameters.
78    ///
79    /// # Returns
80    /// `Some(E)` for [`AttemptFailure::Error`], or `None` for
81    /// runtime-generated failures.
82    ///
83    /// # Errors
84    /// This method does not return errors.
85    #[inline]
86    pub fn into_error(self) -> Option<E> {
87        match self {
88            Self::Error(error) => Some(error),
89            Self::Timeout | Self::Panic(_) | Self::Executor(_) => None,
90        }
91    }
92
93    /// Returns captured panic information when this failure wraps one.
94    ///
95    /// # Parameters
96    /// This method has no parameters.
97    ///
98    /// # Returns
99    /// `Some(&AttemptPanic)` for [`AttemptFailure::Panic`], or `None` for other
100    /// variants.
101    ///
102    /// # Errors
103    /// This method does not return errors.
104    #[inline]
105    pub fn as_panic(&self) -> Option<&AttemptPanic> {
106        match self {
107            Self::Panic(panic) => Some(panic),
108            Self::Error(_) | Self::Timeout | Self::Executor(_) => None,
109        }
110    }
111
112    /// Returns executor failure information when this failure wraps one.
113    ///
114    /// # Parameters
115    /// This method has no parameters.
116    ///
117    /// # Returns
118    /// `Some(&AttemptExecutorError)` for [`AttemptFailure::Executor`], or
119    /// `None` for other variants.
120    ///
121    /// # Errors
122    /// This method does not return errors.
123    #[inline]
124    pub fn as_executor_error(&self) -> Option<&AttemptExecutorError> {
125        match self {
126            Self::Executor(error) => Some(error),
127            Self::Error(_) | Self::Timeout | Self::Panic(_) => None,
128        }
129    }
130}
131
132impl<E: fmt::Display> fmt::Display for AttemptFailure<E> {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        match self {
135            Self::Error(error) => write!(f, "{error}"),
136            Self::Timeout => write!(f, "attempt timed out"),
137            Self::Panic(panic) => write!(f, "attempt panicked: {panic}"),
138            Self::Executor(error) => write!(f, "attempt executor failed: {error}"),
139        }
140    }
141}