xs/store/
ttl.rs

1use std::collections::HashMap;
2use std::time::Duration;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6/// Enum representing the TTL (Time-To-Live) for an event.
7#[derive(Default, PartialEq, Eq, Clone, Debug)]
8pub enum TTL {
9    #[default]
10    Forever, // Event is kept indefinitely.
11    Ephemeral,      // Event is not stored; only active subscribers can see it.
12    Time(Duration), // Event is kept for a custom duration
13    Head(u32),      // Retains only the last n events for a topic (n >= 1).
14}
15
16impl TTL {
17    /// Converts a `TTL` into its query string representation.
18    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    /// Parses a `TTL` from a query string.
28    pub fn from_query(query: Option<&str>) -> Result<Self, String> {
29        // Parse query string into key-value pairs
30        let params = match query {
31            None => return Ok(TTL::default()), // Use default TTL if query is None
32            Some(q) => serde_urlencoded::from_str::<HashMap<String, String>>(q)
33                .map_err(|_| "invalid query string".to_string())?,
34        };
35
36        // Extract the `ttl` parameter if it exists
37        if let Some(ttl_str) = params.get("ttl") {
38            parse_ttl(ttl_str)
39        } else {
40            Ok(TTL::default()) // Use default TTL if `ttl` is not present
41        }
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
71/// Parses a raw TTL string and converts it to the `TTL` enum.
72pub 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}