1use std::{ops::Add, time::Duration};
2
3use chrono::SubsecRound as _;
4
5use crate::Error as LimitationError;
6
7#[derive(Debug, Clone)]
9pub struct Status {
10 pub(crate) limit: usize,
11 pub(crate) remaining: usize,
12 pub(crate) reset_epoch_utc: usize,
13}
14
15impl Status {
16 #[must_use]
18 pub(crate) fn new(count: usize, limit: usize, reset_epoch_utc: usize) -> Self {
19 let remaining = limit.saturating_sub(count);
20
21 Status {
22 limit,
23 remaining,
24 reset_epoch_utc,
25 }
26 }
27
28 #[must_use]
30 pub fn limit(&self) -> usize {
31 self.limit
32 }
33
34 #[must_use]
36 pub fn remaining(&self) -> usize {
37 self.remaining
38 }
39
40 #[must_use]
42 pub fn reset_epoch_utc(&self) -> usize {
43 self.reset_epoch_utc
44 }
45
46 pub(crate) fn epoch_utc_plus(duration: Duration) -> Result<usize, LimitationError> {
47 match chrono::Duration::from_std(duration) {
48 Ok(value) => Ok(chrono::Utc::now()
49 .add(value)
50 .round_subsecs(0)
51 .timestamp()
52 .try_into()
53 .unwrap_or(0)),
54
55 Err(_) => Err(LimitationError::Other(
56 "Source duration value is out of range for the target type".to_string(),
57 )),
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn test_create_status() {
68 let status = Status {
69 limit: 100,
70 remaining: 0,
71 reset_epoch_utc: 1000,
72 };
73
74 assert_eq!(status.limit(), 100);
75 assert_eq!(status.remaining(), 0);
76 assert_eq!(status.reset_epoch_utc(), 1000);
77 }
78
79 #[test]
80 fn test_build_status() {
81 let count = 200;
82 let limit = 100;
83 let status = Status::new(count, limit, 2000);
84 assert_eq!(status.limit(), limit);
85 assert_eq!(status.remaining(), 0);
86 assert_eq!(status.reset_epoch_utc(), 2000);
87 }
88
89 #[test]
90 fn test_build_status_limit() {
91 let limit = 100;
92 let status = Status::new(0, limit, 2000);
93 assert_eq!(status.limit(), limit);
94 assert_eq!(status.remaining(), limit);
95 assert_eq!(status.reset_epoch_utc(), 2000);
96 }
97
98 #[test]
99 fn test_epoch_utc_plus_zero() {
100 let duration = Duration::from_secs(0);
101 let seconds = Status::epoch_utc_plus(duration).unwrap();
102 assert!(seconds as u64 >= duration.as_secs());
103 }
104
105 #[test]
106 fn test_epoch_utc_plus() {
107 let duration = Duration::from_secs(10);
108 let seconds = Status::epoch_utc_plus(duration).unwrap();
109 assert!(seconds as u64 >= duration.as_secs() + 10);
110 }
111
112 #[test]
113 #[should_panic = "Source duration value is out of range for the target type"]
114 fn test_epoch_utc_plus_overflow() {
115 let duration = Duration::from_secs(10000000000000000000);
116 Status::epoch_utc_plus(duration).unwrap();
117 }
118}