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(serialize = "E: serde::Serialize", deserialize = "E: serde::de::DeserializeOwned"))]
33pub enum AttemptFailure<E> {
34    /// The operation returned an application error.
35    Error(E),
36
37    /// The attempt exceeded the effective timeout.
38    ///
39    /// This can be the configured per-attempt timeout, the remaining
40    /// max-operation-elapsed budget, or the remaining max-total-elapsed budget
41    /// used by async and worker-thread attempts.
42    Timeout,
43
44    /// The attempt panicked inside an isolated execution boundary.
45    Panic(AttemptPanic),
46
47    /// The retry executor failed before the attempt could run normally.
48    Executor(AttemptExecutorError),
49}
50
51impl<E> AttemptFailure<E> {
52    /// Returns the application error when this failure wraps one.
53    ///
54    /// # Parameters
55    /// This method has no parameters.
56    ///
57    /// # Returns
58    /// `Some(&E)` for [`AttemptFailure::Error`], or `None` for
59    /// runtime-generated failures.
60    ///
61    /// # Errors
62    /// This method does not return errors.
63    #[inline]
64    pub fn as_error(&self) -> Option<&E> {
65        match self {
66            Self::Error(error) => Some(error),
67            Self::Timeout | Self::Panic(_) | Self::Executor(_) => None,
68        }
69    }
70
71    /// Consumes the failure and returns the application error when present.
72    ///
73    /// # Parameters
74    /// This method has no parameters.
75    ///
76    /// # Returns
77    /// `Some(E)` for [`AttemptFailure::Error`], or `None` for
78    /// runtime-generated failures.
79    ///
80    /// # Errors
81    /// This method does not return errors.
82    #[inline]
83    pub fn into_error(self) -> Option<E> {
84        match self {
85            Self::Error(error) => Some(error),
86            Self::Timeout | Self::Panic(_) | Self::Executor(_) => None,
87        }
88    }
89
90    /// Returns captured panic information when this failure wraps one.
91    ///
92    /// # Parameters
93    /// This method has no parameters.
94    ///
95    /// # Returns
96    /// `Some(&AttemptPanic)` for [`AttemptFailure::Panic`], or `None` for other
97    /// variants.
98    ///
99    /// # Errors
100    /// This method does not return errors.
101    #[inline]
102    pub fn as_panic(&self) -> Option<&AttemptPanic> {
103        match self {
104            Self::Panic(panic) => Some(panic),
105            Self::Error(_) | Self::Timeout | Self::Executor(_) => None,
106        }
107    }
108
109    /// Returns executor failure information when this failure wraps one.
110    ///
111    /// # Parameters
112    /// This method has no parameters.
113    ///
114    /// # Returns
115    /// `Some(&AttemptExecutorError)` for [`AttemptFailure::Executor`], or
116    /// `None` for other variants.
117    ///
118    /// # Errors
119    /// This method does not return errors.
120    #[inline]
121    pub fn as_executor_error(&self) -> Option<&AttemptExecutorError> {
122        match self {
123            Self::Executor(error) => Some(error),
124            Self::Error(_) | Self::Timeout | Self::Panic(_) => None,
125        }
126    }
127}
128
129impl<E: fmt::Display> fmt::Display for AttemptFailure<E> {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        match self {
132            Self::Error(error) => write!(f, "{error}"),
133            Self::Timeout => write!(f, "attempt timed out"),
134            Self::Panic(panic) => write!(f, "attempt panicked: {panic}"),
135            Self::Executor(error) => write!(f, "attempt executor failed: {error}"),
136        }
137    }
138}