use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;
use crate::{AttemptFailure, RetryContext, RetryErrorReason};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound(
serialize = "E: serde::Serialize",
deserialize = "E: serde::de::DeserializeOwned"
))]
pub struct RetryError<E> {
reason: RetryErrorReason,
last_failure: Option<AttemptFailure<E>>,
context: RetryContext,
}
impl<E> RetryError<E> {
#[inline]
pub(crate) fn new(
reason: RetryErrorReason,
last_failure: Option<AttemptFailure<E>>,
context: RetryContext,
) -> Self {
Self {
reason,
last_failure,
context,
}
}
#[cfg(all(coverage, not(test)))]
#[doc(hidden)]
#[inline]
pub fn coverage_new(
reason: RetryErrorReason,
last_failure: Option<AttemptFailure<E>>,
context: RetryContext,
) -> Self {
Self::new(reason, last_failure, context)
}
#[inline]
pub fn reason(&self) -> RetryErrorReason {
self.reason
}
#[inline]
pub fn context(&self) -> &RetryContext {
&self.context
}
#[inline]
pub fn attempt_timeout_source(&self) -> Option<crate::event::AttemptTimeoutSource> {
self.context.attempt_timeout_source()
}
#[inline]
pub fn unreaped_worker_count(&self) -> u32 {
self.context.unreaped_worker_count()
}
#[inline]
pub fn attempts(&self) -> u32 {
self.context.attempt()
}
#[inline]
pub fn last_failure(&self) -> Option<&AttemptFailure<E>> {
self.last_failure.as_ref()
}
#[inline]
pub fn last_error(&self) -> Option<&E> {
self.last_failure().and_then(AttemptFailure::as_error)
}
#[inline]
pub fn into_last_error(self) -> Option<E> {
self.last_failure.and_then(AttemptFailure::into_error)
}
#[inline]
pub fn into_parts(self) -> (RetryErrorReason, Option<AttemptFailure<E>>, RetryContext) {
(self.reason, self.last_failure, self.context)
}
}
impl<E> fmt::Display for RetryError<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let attempts = self.attempts();
let message = match self.reason {
RetryErrorReason::Aborted => format!("retry aborted after {attempts} attempt(s)"),
RetryErrorReason::AttemptsExceeded => format!(
"retry attempts exceeded after {attempts} attempt(s), max {}",
self.context.max_attempts()
),
RetryErrorReason::MaxOperationElapsedExceeded => {
format!("retry max operation elapsed exceeded after {attempts} attempt(s)")
}
RetryErrorReason::MaxTotalElapsedExceeded => {
format!("retry max total elapsed exceeded after {attempts} attempt(s)")
}
RetryErrorReason::UnsupportedOperation => {
"run() does not support attempt timeout; use run_async() or run_in_worker()"
.to_string()
}
RetryErrorReason::WorkerStillRunning => {
format!(
"retry worker still running after timeout cancellation grace, unreaped {}",
self.context.unreaped_worker_count()
)
}
};
f.write_str(&message)?;
if let Some(failure) = &self.last_failure {
write!(f, "; last failure: {failure}")?;
}
Ok(())
}
}
impl<E> Error for RetryError<E>
where
E: Error + 'static,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self.last_failure() {
Some(AttemptFailure::Error(error)) => Some(error as &(dyn Error + 'static)),
Some(AttemptFailure::Panic(panic)) => Some(panic as &(dyn Error + 'static)),
Some(AttemptFailure::Executor(error)) => Some(error as &(dyn Error + 'static)),
Some(AttemptFailure::Timeout) | None => None,
}
}
}