1use std::fmt::Display;
16use std::time::Duration;
17
18use serde::{Deserialize, Serialize};
19use snafu::ResultExt;
20
21use crate::error::{Error, InvalidDatabaseTtlSnafu, ParseDurationSnafu};
22use crate::Timestamp;
23
24pub const INSTANT: &str = "instant";
25pub const FOREVER: &str = "forever";
26
27#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum DatabaseTimeToLive {
33 #[default]
35 Forever,
36 #[serde(untagged, with = "humantime_serde")]
38 Duration(Duration),
39}
40
41impl Display for DatabaseTimeToLive {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 DatabaseTimeToLive::Forever => write!(f, "{}", FOREVER),
45 DatabaseTimeToLive::Duration(d) => write!(f, "{}", humantime::Duration::from(*d)),
46 }
47 }
48}
49
50impl DatabaseTimeToLive {
51 pub fn from_humantime_or_str(s: &str) -> Result<Self, Error> {
55 let ttl = match s.to_lowercase().as_ref() {
56 INSTANT => InvalidDatabaseTtlSnafu.fail()?,
57 FOREVER | "" => Self::Forever,
58 _ => {
59 let d = humantime::parse_duration(s).context(ParseDurationSnafu)?;
60 Self::from(d)
61 }
62 };
63 Ok(ttl)
64 }
65}
66
67impl TryFrom<TimeToLive> for DatabaseTimeToLive {
68 type Error = Error;
69 fn try_from(value: TimeToLive) -> Result<Self, Self::Error> {
70 match value {
71 TimeToLive::Instant => InvalidDatabaseTtlSnafu.fail()?,
72 TimeToLive::Forever => Ok(Self::Forever),
73 TimeToLive::Duration(d) => Ok(Self::from(d)),
74 }
75 }
76}
77
78impl From<DatabaseTimeToLive> for TimeToLive {
79 fn from(value: DatabaseTimeToLive) -> Self {
80 match value {
81 DatabaseTimeToLive::Forever => TimeToLive::Forever,
82 DatabaseTimeToLive::Duration(d) => TimeToLive::from(d),
83 }
84 }
85}
86
87impl From<Duration> for DatabaseTimeToLive {
88 fn from(duration: Duration) -> Self {
89 if duration.is_zero() {
90 Self::Forever
91 } else {
92 Self::Duration(duration)
93 }
94 }
95}
96
97impl From<humantime::Duration> for DatabaseTimeToLive {
98 fn from(duration: humantime::Duration) -> Self {
99 Self::from(*duration)
100 }
101}
102
103#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum TimeToLive {
107 Instant,
109 #[default]
111 Forever,
112 #[serde(untagged, with = "humantime_serde")]
114 Duration(Duration),
115}
116
117impl Display for TimeToLive {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match self {
120 TimeToLive::Instant => write!(f, "{}", INSTANT),
121 TimeToLive::Duration(d) => write!(f, "{}", humantime::Duration::from(*d)),
122 TimeToLive::Forever => write!(f, "{}", FOREVER),
123 }
124 }
125}
126
127impl TimeToLive {
128 pub fn from_humantime_or_str(s: &str) -> Result<Self, Error> {
132 match s.to_lowercase().as_ref() {
133 INSTANT => Ok(TimeToLive::Instant),
134 FOREVER | "" => Ok(TimeToLive::Forever),
135 _ => {
136 let d = humantime::parse_duration(s).context(ParseDurationSnafu)?;
137 Ok(TimeToLive::from(d))
138 }
139 }
140 }
141
142 pub fn is_expired(
145 &self,
146 created_at: &Timestamp,
147 now: &Timestamp,
148 ) -> crate::error::Result<bool> {
149 Ok(match self {
150 TimeToLive::Instant => true,
151 TimeToLive::Forever => false,
152 TimeToLive::Duration(d) => now.sub_duration(*d)? > *created_at,
153 })
154 }
155
156 pub fn is_instant(&self) -> bool {
158 matches!(self, TimeToLive::Instant)
159 }
160
161 pub fn is_forever(&self) -> bool {
163 matches!(self, TimeToLive::Forever)
164 }
165}
166
167impl From<Duration> for TimeToLive {
168 fn from(duration: Duration) -> Self {
169 if duration.is_zero() {
170 TimeToLive::Forever
172 } else {
173 TimeToLive::Duration(duration)
174 }
175 }
176}
177
178impl From<humantime::Duration> for TimeToLive {
179 fn from(duration: humantime::Duration) -> Self {
180 Self::from(*duration)
181 }
182}
183
184#[cfg(test)]
185mod test {
186 use super::*;
187
188 #[test]
189 fn test_db_ttl_table_ttl() {
190 let ttl = TimeToLive::from(Duration::from_secs(10));
192 let db_ttl: DatabaseTimeToLive = ttl.try_into().unwrap();
193 assert_eq!(db_ttl, DatabaseTimeToLive::from(Duration::from_secs(10)));
194 assert_eq!(TimeToLive::from(db_ttl), ttl);
195
196 let ttl = TimeToLive::from(Duration::from_secs(0));
197 let db_ttl: DatabaseTimeToLive = ttl.try_into().unwrap();
198 assert_eq!(db_ttl, DatabaseTimeToLive::Forever);
199 assert_eq!(TimeToLive::from(db_ttl), ttl);
200
201 let ttl = TimeToLive::Instant;
202 let err_instant = DatabaseTimeToLive::try_from(ttl);
203 assert!(err_instant.is_err());
204
205 let ttl = Duration::from_secs(0);
207 let db_ttl: DatabaseTimeToLive = ttl.into();
208 assert_eq!(db_ttl, DatabaseTimeToLive::Forever);
209
210 let ttl = Duration::from_secs(10);
211 let db_ttl: DatabaseTimeToLive = ttl.into();
212 assert_eq!(
213 db_ttl,
214 DatabaseTimeToLive::Duration(Duration::from_secs(10))
215 );
216
217 let ttl = DatabaseTimeToLive::from_humantime_or_str("10s").unwrap();
218 let ttl: TimeToLive = ttl.into();
219 assert_eq!(ttl, TimeToLive::from(Duration::from_secs(10)));
220
221 let ttl = DatabaseTimeToLive::from_humantime_or_str("forever").unwrap();
222 let ttl: TimeToLive = ttl.into();
223 assert_eq!(ttl, TimeToLive::Forever);
224
225 assert!(DatabaseTimeToLive::from_humantime_or_str("instant").is_err());
226
227 let ttl = DatabaseTimeToLive::from_humantime_or_str("0s").unwrap();
229 let ttl: TimeToLive = ttl.into();
230 assert_eq!(ttl, TimeToLive::Forever);
231 }
232
233 #[test]
234 fn test_serde() {
235 let cases = vec![
236 ("\"instant\"", TimeToLive::Instant),
237 ("\"forever\"", TimeToLive::Forever),
238 ("\"10d\"", Duration::from_secs(86400 * 10).into()),
239 (
240 "\"10000 years\"",
241 humantime::parse_duration("10000 years").unwrap().into(),
242 ),
243 ];
244
245 for (s, expected) in cases {
246 let serialized = serde_json::to_string(&expected).unwrap();
247 let deserialized: TimeToLive = serde_json::from_str(&serialized).unwrap();
248 assert_eq!(deserialized, expected);
249
250 let deserialized: TimeToLive = serde_json::from_str(s).unwrap_or_else(|err| {
251 panic!("Actual serialized: {}, s=`{s}`, err: {:?}", serialized, err)
252 });
253 assert_eq!(deserialized, expected);
254
255 if s == "\"instant\"" {
257 assert!(serde_json::from_str::<DatabaseTimeToLive>(s).is_err());
258 continue;
259 }
260
261 let db_ttl: DatabaseTimeToLive = serde_json::from_str(s).unwrap();
262 let re_serialized = serde_json::to_string(&db_ttl).unwrap();
263 assert_eq!(re_serialized, serialized);
264 }
265 }
266}