/*******************************************************************************
 *
 *    Copyright (c) 2025 - 2026.
 *    Haixing Hu, Qubit Co. Ltd.
 *
 *    All rights reserved.
 *
 ******************************************************************************/
//! Attempt-level failure values.
//!
//! A retry failure describes why one operation attempt did not produce a
//! successful result. It is distinct from [`crate::RetryError`], which describes
//! why the whole retry flow stopped.

use std::fmt;

use serde::{Deserialize, Serialize};

/// Failure produced by a single operation attempt.
///
/// The generic parameter `E` is the caller's operation error type. Timeout
/// failures do not contain `E` because they are generated by the retry runtime,
/// not returned by the operation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(bound(
    serialize = "E: serde::Serialize",
    deserialize = "E: serde::de::DeserializeOwned"
))]
pub enum AttemptFailure<E> {
    /// The operation returned an application error.
    Error(E),

    /// The attempt exceeded the configured per-attempt timeout.
    Timeout,
}

impl<E> AttemptFailure<E> {
    /// Returns the application error when this failure wraps one.
    ///
    /// # Parameters
    /// This method has no parameters.
    ///
    /// # Returns
    /// `Some(&E)` for [`AttemptFailure::Error`], or `None` for
    /// [`AttemptFailure::Timeout`].
    ///
    /// # Errors
    /// This method does not return errors.
    #[inline]
    pub fn as_error(&self) -> Option<&E> {
        match self {
            Self::Error(error) => Some(error),
            Self::Timeout => None,
        }
    }

    /// Consumes the failure and returns the application error when present.
    ///
    /// # Parameters
    /// This method has no parameters.
    ///
    /// # Returns
    /// `Some(E)` for [`AttemptFailure::Error`], or `None` for
    /// [`AttemptFailure::Timeout`].
    ///
    /// # Errors
    /// This method does not return errors.
    #[inline]
    pub fn into_error(self) -> Option<E> {
        match self {
            Self::Error(error) => Some(error),
            Self::Timeout => None,
        }
    }
}

impl<E: fmt::Display> fmt::Display for AttemptFailure<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Error(error) => write!(f, "{error}"),
            Self::Timeout => write!(f, "attempt timed out"),
        }
    }
}
