appletheia_application/outbox/
outbox_next_attempt_at.rs1use 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}