Skip to main content

appletheia_application/outbox/
outbox_attempt_count.rs

1use std::{fmt, fmt::Display};
2
3use super::OutboxAttemptCountError;
4
5#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
6pub struct OutboxAttemptCount(i64);
7
8impl OutboxAttemptCount {
9    pub fn new() -> Self {
10        Self(0)
11    }
12
13    pub fn value(&self) -> i64 {
14        self.0
15    }
16
17    pub fn increment(self) -> Self {
18        Self(self.value() + 1)
19    }
20
21    pub fn checked_increment(self) -> Option<Self> {
22        self.value().checked_add(1).map(Self)
23    }
24
25    pub fn try_increment(self) -> Result<Self, OutboxAttemptCountError> {
26        self.checked_increment()
27            .ok_or(OutboxAttemptCountError::Overflow)
28    }
29}
30
31impl Default for OutboxAttemptCount {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl TryFrom<i64> for OutboxAttemptCount {
38    type Error = OutboxAttemptCountError;
39
40    fn try_from(value: i64) -> Result<Self, Self::Error> {
41        if value < 0 {
42            Err(OutboxAttemptCountError::NegativeValue(value))
43        } else {
44            Ok(Self(value))
45        }
46    }
47}
48
49impl From<OutboxAttemptCount> for i64 {
50    fn from(value: OutboxAttemptCount) -> Self {
51        value.value()
52    }
53}
54
55impl Display for OutboxAttemptCount {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "{}", self.value())
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn new_starts_at_zero() {
67        let count = OutboxAttemptCount::new();
68        assert_eq!(count.value(), 0);
69    }
70
71    #[test]
72    fn try_from_accepts_non_negative_values() {
73        let count = OutboxAttemptCount::try_from(5).expect("expected valid count");
74        assert_eq!(count.value(), 5);
75    }
76
77    #[test]
78    fn try_from_rejects_negative_values() {
79        let err = OutboxAttemptCount::try_from(-1).expect_err("expected negative value error");
80        match err {
81            OutboxAttemptCountError::NegativeValue(v) => assert_eq!(v, -1),
82            _ => panic!("unexpected error variant"),
83        }
84    }
85
86    #[test]
87    fn increment_increments_count() {
88        let current = OutboxAttemptCount::new();
89        let next = current.increment();
90        assert_eq!(next.value(), 1);
91    }
92
93    #[test]
94    fn checked_increment_handles_overflow() {
95        let near_max = OutboxAttemptCount::try_from(i64::MAX - 1).unwrap();
96        let next = near_max
97            .checked_increment()
98            .expect("should provide next value");
99        assert_eq!(next.value(), i64::MAX);
100
101        let max = OutboxAttemptCount::try_from(i64::MAX).unwrap();
102        assert!(max.checked_increment().is_none());
103    }
104
105    #[test]
106    fn try_increment_returns_error_on_overflow() {
107        let near_max = OutboxAttemptCount::try_from(i64::MAX - 1).unwrap();
108        let next = near_max.try_increment().expect("should provide next value");
109        assert_eq!(next.value(), i64::MAX);
110
111        let max = OutboxAttemptCount::try_from(i64::MAX).unwrap();
112        let err = max.try_increment().expect_err("expected overflow error");
113        match err {
114            OutboxAttemptCountError::Overflow => {}
115            _ => panic!("unexpected error variant"),
116        }
117    }
118}