Skip to main content

appletheia_application/outbox/
outbox_next_attempt_at.rs

1use std::{fmt, fmt::Display};
2
3use chrono::{DateTime, Utc};
4
5use super::OutboxRetryDelay;
6
7#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
8pub struct OutboxNextAttemptAt(DateTime<Utc>);
9
10impl OutboxNextAttemptAt {
11    pub fn now() -> Self {
12        Self(Utc::now())
13    }
14
15    pub fn value(&self) -> DateTime<Utc> {
16        self.0
17    }
18
19    pub fn next(self, backoff: OutboxRetryDelay) -> Self {
20        Self(self.value() + backoff.value())
21    }
22}
23
24impl Default for OutboxNextAttemptAt {
25    fn default() -> Self {
26        Self::now()
27    }
28}
29
30impl From<DateTime<Utc>> for OutboxNextAttemptAt {
31    fn from(value: DateTime<Utc>) -> Self {
32        Self(value)
33    }
34}
35
36impl From<OutboxNextAttemptAt> for DateTime<Utc> {
37    fn from(value: OutboxNextAttemptAt) -> Self {
38        value.0
39    }
40}
41
42impl Display for OutboxNextAttemptAt {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "{}", self.value())
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use chrono::Duration;
52
53    #[test]
54    fn default_uses_now() {
55        let before = Utc::now();
56        let next_attempt = OutboxNextAttemptAt::default();
57        let after = Utc::now();
58
59        let value = next_attempt.value();
60        assert!(value >= before, "expected {value} to be after {before}");
61        assert!(value <= after, "expected {value} to be before {after}");
62    }
63
64    #[test]
65    fn value_returns_inner_datetime() {
66        let t = Utc::now();
67        let next_attempt = OutboxNextAttemptAt::from(t);
68        assert_eq!(next_attempt.value(), t);
69    }
70
71    #[test]
72    fn next_applies_backoff_duration() {
73        let base = Utc::now();
74        let next_attempt = OutboxNextAttemptAt::from(base);
75        let backoff = OutboxRetryDelay::from(Duration::seconds(30));
76
77        let advanced = next_attempt.next(backoff);
78        assert_eq!(advanced.value(), base + backoff.value());
79    }
80
81    #[test]
82    fn conversions_round_trip() {
83        let t = Utc::now();
84        let wrapped: OutboxNextAttemptAt = t.into();
85        let back: DateTime<Utc> = wrapped.into();
86        assert_eq!(back, t);
87    }
88
89    #[test]
90    fn display_matches_inner_datetime() {
91        let t = Utc::now();
92        let wrapped = OutboxNextAttemptAt::from(t);
93        assert_eq!(wrapped.to_string(), t.to_string());
94    }
95}