1use std::collections::HashMap;
2use std::time::Duration;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6#[derive(Default, PartialEq, Eq, Clone, Debug)]
8pub enum TTL {
9 #[default]
10 Forever, Ephemeral, Time(Duration), Head(u32), }
15
16impl TTL {
17 pub fn to_query(&self) -> String {
19 match self {
20 TTL::Forever => "ttl=forever".to_string(),
21 TTL::Ephemeral => "ttl=ephemeral".to_string(),
22 TTL::Time(duration) => format!("ttl=time:{millis}", millis = duration.as_millis()),
23 TTL::Head(n) => format!("ttl=head:{n}"),
24 }
25 }
26
27 pub fn from_query(query: Option<&str>) -> Result<Self, String> {
29 let params = match query {
31 None => return Ok(TTL::default()), Some(q) => serde_urlencoded::from_str::<HashMap<String, String>>(q)
33 .map_err(|_| "invalid query string".to_string())?,
34 };
35
36 if let Some(ttl_str) = params.get("ttl") {
38 parse_ttl(ttl_str)
39 } else {
40 Ok(TTL::default()) }
42 }
43}
44
45impl Serialize for TTL {
46 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
47 where
48 S: Serializer,
49 {
50 match self {
51 TTL::Forever => serializer.serialize_str("forever"),
52 TTL::Ephemeral => serializer.serialize_str("ephemeral"),
53 TTL::Time(duration) => {
54 serializer.serialize_str(&format!("time:{millis}", millis = duration.as_millis()))
55 }
56 TTL::Head(n) => serializer.serialize_str(&format!("head:{n}")),
57 }
58 }
59}
60
61impl<'de> Deserialize<'de> for TTL {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: Deserializer<'de>,
65 {
66 let s: String = Deserialize::deserialize(deserializer)?;
67 parse_ttl(&s).map_err(serde::de::Error::custom)
68 }
69}
70
71pub fn parse_ttl(s: &str) -> Result<TTL, String> {
73 match s {
74 "forever" => Ok(TTL::Forever),
75 "ephemeral" => Ok(TTL::Ephemeral),
76 _ if s.starts_with("time:") => {
77 let duration_str = &s[5..];
78 let duration = duration_str
79 .parse::<u64>()
80 .map_err(|_| "Invalid duration for 'time' TTL".to_string())?;
81 Ok(TTL::Time(Duration::from_millis(duration)))
82 }
83 _ if s.starts_with("head:") => {
84 let n_str = &s[5..];
85 let n = n_str
86 .parse::<u32>()
87 .map_err(|_| "Invalid 'n' value for 'head' TTL".to_string())?;
88 if n < 1 {
89 Err("'n' must be >= 1 for 'head' TTL".to_string())
90 } else {
91 Ok(TTL::Head(n))
92 }
93 }
94 _ => Err("Invalid TTL format".to_string()),
95 }
96}