stellar_base/
time_bounds.rs1use std::io::{Read, Write};
3
4use crate::error::{Error, Result};
5use crate::xdr;
6use chrono::{DateTime, Duration, TimeZone, Utc};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct TimeBounds {
11 lower: Option<DateTime<Utc>>,
12 upper: Option<DateTime<Utc>>,
13}
14
15impl TimeBounds {
16 pub fn valid_for(duration: Duration) -> TimeBounds {
18 let lower = Utc::now();
19 let upper = lower + duration;
20 TimeBounds {
21 lower: None,
22 upper: Some(upper),
23 }
24 }
25
26 pub fn always_valid() -> TimeBounds {
28 TimeBounds {
29 lower: None,
30 upper: None,
31 }
32 }
33
34 pub fn with_lower(&self, lower: DateTime<Utc>) -> Result<TimeBounds> {
36 ensure_valid_timestamp(&lower)?;
37 match self.upper {
38 Some(upper) if upper < lower => Err(Error::InvalidTimeBounds),
39 Some(upper) => Ok(TimeBounds {
40 lower: Some(lower),
41 upper: Some(upper),
42 }),
43 None => Ok(TimeBounds {
44 lower: Some(lower),
45 upper: None,
46 }),
47 }
48 }
49
50 pub fn with_upper(&self, upper: DateTime<Utc>) -> Result<TimeBounds> {
52 ensure_valid_timestamp(&upper)?;
53 match self.lower {
54 Some(lower) if upper < lower => Err(Error::InvalidTimeBounds),
55 Some(lower) => Ok(TimeBounds {
56 lower: Some(lower),
57 upper: Some(upper),
58 }),
59 None => Ok(TimeBounds {
60 lower: None,
61 upper: Some(upper),
62 }),
63 }
64 }
65
66 pub fn lower(&self) -> &Option<DateTime<Utc>> {
68 &self.lower
69 }
70
71 pub fn lower_mut(&mut self) -> &mut Option<DateTime<Utc>> {
73 &mut self.lower
74 }
75
76 pub fn upper(&self) -> &Option<DateTime<Utc>> {
78 &self.upper
79 }
80
81 pub fn upper_mut(&mut self) -> &mut Option<DateTime<Utc>> {
83 &mut self.upper
84 }
85
86 pub fn to_xdr(&self) -> Result<xdr::TimeBounds> {
88 let min_time: u64 = match self.lower {
89 None => 0,
90 Some(t) => t.timestamp() as u64,
91 };
92 let min_time = xdr::TimePoint(min_time);
93 let max_time: u64 = match self.upper {
94 None => 0,
95 Some(t) => t.timestamp() as u64,
96 };
97 let max_time = xdr::TimePoint(max_time);
98 Ok(xdr::TimeBounds { min_time, max_time })
99 }
100
101 pub fn from_xdr(x: &xdr::TimeBounds) -> Result<TimeBounds> {
103 let min_time_epoch = x.min_time.0 as i64;
104 let max_time_epoch = x.max_time.0 as i64;
105
106 let mut res = TimeBounds::always_valid();
107
108 if min_time_epoch != 0 {
109 res = res.with_lower(
110 Utc.timestamp_opt(min_time_epoch, 0)
111 .single()
112 .ok_or(Error::InvalidTimeBounds)?,
113 )?;
114 }
115 if max_time_epoch != 0 {
116 res = res.with_upper(
117 Utc.timestamp_opt(max_time_epoch, 0)
118 .single()
119 .ok_or(Error::InvalidTimeBounds)?,
120 )?;
121 }
122
123 Ok(res)
124 }
125}
126
127impl xdr::WriteXdr for TimeBounds {
128 fn write_xdr<W: Write>(&self, w: &mut xdr::Limited<W>) -> xdr::Result<()> {
129 let xdr = self.to_xdr().map_err(|_| xdr::Error::Invalid)?;
130 xdr.write_xdr(w)
131 }
132}
133
134impl xdr::ReadXdr for TimeBounds {
135 fn read_xdr<R: Read>(r: &mut xdr::Limited<R>) -> xdr::Result<Self> {
136 let xdr_result = xdr::TimeBounds::read_xdr(r)?;
137 Self::from_xdr(&xdr_result).map_err(|_| xdr::Error::Invalid)
138 }
139}
140
141fn ensure_valid_timestamp(dt: &DateTime<Utc>) -> Result<()> {
142 let ts = dt.timestamp();
143 if ts >= 0 {
144 Ok(())
145 } else {
146 Err(Error::InvalidTimeBounds)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::TimeBounds;
153 use crate::xdr::{XDRDeserialize, XDRSerialize};
154 use chrono::{DateTime, Datelike, Duration, Utc};
155
156 #[test]
157 fn test_valid_for() {
158 let five_min = Duration::minutes(5);
159 let tb = TimeBounds::valid_for(five_min);
160 assert_eq!(None, *tb.lower());
161 assert_ne!(None, *tb.upper());
162 }
163
164 #[test]
165 fn test_always_valid() {
166 let tb = TimeBounds::always_valid();
167 assert_eq!(None, *tb.lower());
168 assert_eq!(None, *tb.upper());
169 }
170
171 #[test]
172 fn test_with_upper_success() {
173 let tb = TimeBounds::always_valid().with_upper(Utc::now()).unwrap();
174 assert_eq!(None, *tb.lower());
175 assert_ne!(None, *tb.upper());
176 }
177
178 #[test]
179 fn test_with_lower_success() {
180 let tb = TimeBounds::always_valid().with_lower(Utc::now()).unwrap();
181 assert_ne!(None, *tb.lower());
182 assert_eq!(None, *tb.upper());
183 }
184
185 #[test]
186 fn test_with_both_success() {
187 let now = Utc::now();
188 let before_now = now - Duration::minutes(1);
189 let tb = TimeBounds::always_valid()
190 .with_lower(before_now)
191 .unwrap()
192 .with_upper(now)
193 .unwrap();
194 assert_ne!(None, *tb.lower());
195 assert_ne!(None, *tb.upper());
196 }
197
198 #[test]
199 fn test_with_upper_before_the_seventies() {
200 let res = TimeBounds::always_valid().with_upper(Utc::now().with_year(1960).unwrap());
201 assert!(res.is_err());
202 }
203
204 #[test]
205 fn test_with_lower_before_the_seventies() {
206 let res = TimeBounds::always_valid().with_lower(Utc::now().with_year(1960).unwrap());
207 assert!(res.is_err());
208 }
209
210 #[test]
211 fn test_with_upper_before_lower() {
212 let now = Utc::now();
213 let before_now = now - Duration::minutes(1);
214 let res = TimeBounds::always_valid()
215 .with_lower(now)
216 .unwrap()
217 .with_upper(before_now);
218 assert!(res.is_err());
219 }
220
221 #[test]
222 fn test_with_lower_after_upper() {
223 let now = Utc::now();
224 let before_now = now - Duration::minutes(1);
225 let res = TimeBounds::always_valid()
226 .with_upper(before_now)
227 .unwrap()
228 .with_lower(now);
229 assert!(res.is_err());
230 }
231
232 #[test]
233 fn test_serialize_always_valid() {
234 let tb = TimeBounds::always_valid();
235 let xdr = tb.xdr_base64().unwrap();
236 assert_eq!("AAAAAAAAAAAAAAAAAAAAAA==", xdr);
237 }
238
239 #[test]
240 fn test_serialize_with_bounds() {
241 let now = DateTime::<Utc>::from_timestamp(1594305941, 0).unwrap();
242 let before_now = now - Duration::minutes(1);
243 let tb = TimeBounds::always_valid()
244 .with_lower(before_now)
245 .unwrap()
246 .with_upper(now)
247 .unwrap();
248 let xdr = tb.xdr_base64().unwrap();
249 assert_eq!("AAAAAF8HLVkAAAAAXwctlQ==", xdr);
250 }
251
252 #[test]
253 fn test_deserialize_always_valid() {
254 let expected = TimeBounds::always_valid();
255 let tb = TimeBounds::from_xdr_base64("AAAAAAAAAAAAAAAAAAAAAA==").unwrap();
256 assert_eq!(expected, tb);
257 }
258
259 #[test]
260 fn test_deserialize_with_bounds() {
261 let now = DateTime::<Utc>::from_timestamp(1594305941, 0).unwrap();
262 let before_now = now - Duration::minutes(1);
263 let expected = TimeBounds::always_valid()
264 .with_lower(before_now)
265 .unwrap()
266 .with_upper(now)
267 .unwrap();
268 let tb = TimeBounds::from_xdr_base64("AAAAAF8HLVkAAAAAXwctlQ==").unwrap();
269 assert_eq!(expected, tb);
270 }
271}