#![allow(dead_code, reason = "need to work across many combinations of features, let's just allow it")]
use std::fmt::Display;
pub(crate) const ATTEMPT_INDEX: &str = "resilience.attempt.index";
pub(crate) const ATTEMPT_IS_LAST: &str = "resilience.attempt.is_last";
pub(crate) const ATTEMPT_RECOVERY_KIND: &str = "resilience.attempt.recovery.kind";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Attempt {
index: u32,
is_last: bool,
}
impl Default for Attempt {
fn default() -> Self {
Self::new(0, true)
}
}
impl Attempt {
#[must_use]
pub fn new(index: u32, is_last: bool) -> Self {
Self { index, is_last }
}
#[must_use]
pub fn is_first(self) -> bool {
self.index == 0
}
#[must_use]
pub fn is_last(self) -> bool {
self.is_last
}
#[must_use]
pub fn index(self) -> u32 {
self.index
}
#[cfg_attr(test, mutants::skip)] #[cfg(any(feature = "retry", feature = "hedging", test))]
pub(crate) fn increment(self, max_attempts: u32) -> Option<Self> {
let next = self.index.saturating_add(1);
if next >= max_attempts {
return None;
}
let is_last = next == max_attempts.saturating_sub(1);
Some(Self::new(next, is_last))
}
#[cfg(any(feature = "retry", test))]
pub(crate) fn first(max_attempts: u32) -> Self {
Self::new(0, max_attempts == 1)
}
}
impl Display for Attempt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.index.fmt(f)
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attributes() {
assert_eq!(ATTEMPT_INDEX, "resilience.attempt.index");
assert_eq!(ATTEMPT_IS_LAST, "resilience.attempt.is_last");
assert_eq!(ATTEMPT_RECOVERY_KIND, "resilience.attempt.recovery.kind");
}
#[test]
fn new_with_zero_is_first_and_not_last() {
let a = Attempt::new(0, false);
assert_eq!(a.index(), 0);
assert!(a.is_first());
assert!(!a.is_last());
}
#[test]
fn new_when_equal_to_max_is_last() {
let a = Attempt::new(5, true);
assert!(a.is_last());
assert!(!a.is_first());
}
#[test]
fn new_with_zero_max_is_both_first_and_last() {
let a = Attempt::new(0, true);
assert!(a.is_first());
assert!(a.is_last());
}
#[test]
fn increment_correct_behavior() {
let max_attempts = 2;
let a = Attempt::new(0, false);
assert_eq!(a.index(), 0);
assert!(!a.is_last());
let a = a.increment(max_attempts).unwrap();
assert_eq!(a.index(), 1);
assert!(a.is_last());
let a = a.increment(max_attempts);
assert!(a.is_none());
}
#[test]
fn display_shows_index() {
let a = Attempt::new(42, false);
assert_eq!(format!("{a}"), "42");
}
#[test]
fn first_attempt_returns_correct_attempt() {
let first = Attempt::first(3);
assert_eq!(first.index(), 0);
assert!(!first.is_last());
let first_one = Attempt::first(1);
assert_eq!(first_one.index(), 0);
assert!(first_one.is_last());
}
#[test]
fn default_ok() {
let default = Attempt::default();
assert_eq!(default.index(), 0);
assert!(default.is_first());
assert!(default.is_last());
}
}