leptos_query_rs/types/
mod.rs

1//! Core types and data structures for the query system
2
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::{Duration, Instant};
5use serde::{Serialize, Deserialize};
6use std::fmt;
7
8/// Query status enum
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub enum QueryStatus {
11    /// Query is idle (not running)
12    Idle,
13    /// Query is currently loading
14    Loading,
15    /// Query completed successfully
16    Success,
17    /// Query failed with an error
18    Error,
19}
20
21impl Default for QueryStatus {
22    fn default() -> Self {
23        Self::Idle
24    }
25}
26
27/// Query key for identifying queries in the cache
28#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
29pub struct QueryKey {
30    pub segments: Vec<String>,
31}
32
33impl fmt::Display for QueryKey {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "{}", self.segments.join(":"))
36    }
37}
38
39impl QueryKey {
40    /// Create a new query key from segments
41    pub fn new(segments: impl IntoIterator<Item = impl ToString>) -> Self {
42        Self {
43            segments: segments.into_iter().map(|s| s.to_string()).collect(),
44        }
45    }
46    
47    /// Create a query key from a single string
48    pub fn from_string(s: impl Into<String>) -> Self {
49        Self {
50            segments: vec![s.into()],
51        }
52    }
53    
54    /// Add a segment to the key
55    pub fn with_segment(mut self, segment: impl Into<String>) -> Self {
56        self.segments.push(segment.into());
57        self
58    }
59    
60    /// Get the segments as a slice
61    pub fn segments(&self) -> &[String] {
62        &self.segments
63    }
64    
65    /// Check if the key is empty
66    pub fn is_empty(&self) -> bool {
67        self.segments.is_empty()
68    }
69    
70    /// Get the number of segments
71    pub fn len(&self) -> usize {
72        self.segments.len()
73    }
74    
75    /// Check if this key matches a pattern
76    pub fn matches_pattern(&self, pattern: &QueryKeyPattern) -> bool {
77        match pattern {
78            QueryKeyPattern::Exact(key) => self == key,
79            QueryKeyPattern::Prefix(prefix) => {
80                self.segments.len() >= prefix.segments.len() &&
81                self.segments[..prefix.segments.len()] == prefix.segments
82            }
83            QueryKeyPattern::Contains(substring) => {
84                self.segments.iter().any(|segment| segment.contains(substring))
85            }
86        }
87    }
88}
89
90/// Convert string slices to QueryKey
91impl<T: ToString + std::fmt::Display> From<&[T]> for QueryKey {
92    fn from(segments: &[T]) -> Self {
93        Self::new(segments)
94    }
95}
96
97/// Convert single string to QueryKey
98impl From<String> for QueryKey {
99    fn from(segment: String) -> Self {
100        Self::new([segment])
101    }
102}
103
104/// Convert &str to QueryKey
105impl From<&str> for QueryKey {
106    fn from(segment: &str) -> Self {
107        Self::new([segment.to_string()])
108    }
109}
110
111/// Convert tuple to QueryKey  
112impl<T: ToString + std::fmt::Display> From<(T,)> for QueryKey {
113    fn from((a,): (T,)) -> Self {
114        Self::new([a])
115    }
116}
117
118/// Convert array to QueryKey
119impl<const N: usize> From<[&str; N]> for QueryKey {
120    fn from(segments: [&str; N]) -> Self {
121        Self::new(&segments)
122    }
123}
124
125/// Patterns for matching query keys
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub enum QueryKeyPattern {
128    /// Exact match
129    Exact(QueryKey),
130    /// Prefix match (key starts with this pattern)
131    Prefix(QueryKey),
132    /// Contains substring match
133    Contains(String),
134}
135
136/// Observer ID for tracking query observers
137#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
138pub struct QueryObserverId {
139    pub id: u64,
140}
141
142impl QueryObserverId {
143    /// Create a new observer ID
144    pub fn new() -> Self {
145        static COUNTER: AtomicU64 = AtomicU64::new(0);
146        Self {
147            id: COUNTER.fetch_add(1, Ordering::Relaxed),
148        }
149    }
150}
151
152impl Default for QueryObserverId {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158/// Metadata about a query
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct QueryMeta {
161    pub status: QueryStatus,
162    #[serde(with = "instant_serde")]
163    pub updated_at: Instant,
164    #[serde(with = "duration_serde")]
165    pub stale_time: Duration,
166    #[serde(with = "duration_serde")]
167    pub cache_time: Duration,
168}
169
170impl QueryMeta {
171    /// Check if the query is stale
172    pub fn is_stale(&self) -> bool {
173        let age = Instant::now().duration_since(self.updated_at);
174        age > self.stale_time
175    }
176    
177    /// Check if the query has expired
178    pub fn is_expired(&self) -> bool {
179        let age = Instant::now().duration_since(self.updated_at);
180        age > self.cache_time
181    }
182}
183
184impl Default for QueryMeta {
185    fn default() -> Self {
186        Self {
187            status: QueryStatus::Idle,
188            updated_at: Instant::now(),
189            stale_time: Duration::from_secs(0),
190            cache_time: Duration::from_secs(5 * 60), // 5 minutes
191        }
192    }
193}
194
195/// Serialization helpers for Instant
196mod instant_serde {
197    use serde::{Deserialize, Deserializer, Serialize, Serializer};
198    use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
199
200    pub fn serialize<S>(instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
201    where
202        S: Serializer,
203    {
204        // Convert Instant to SystemTime for serialization
205        let system_time = SystemTime::now() - instant.elapsed();
206        let duration = system_time.duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO);
207        duration.serialize(serializer)
208    }
209
210    pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
211    where
212        D: Deserializer<'de>,
213    {
214        let duration = Duration::deserialize(deserializer)?;
215        let system_time = UNIX_EPOCH + duration;
216        let now = SystemTime::now();
217        let elapsed = now.duration_since(system_time).unwrap_or(Duration::ZERO);
218        Ok(Instant::now() - elapsed)
219    }
220}
221
222/// Serialization helpers for Duration
223mod duration_serde {
224    use serde::{Deserialize, Deserializer, Serialize, Serializer};
225    use std::time::Duration;
226
227    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
228    where
229        S: Serializer,
230    {
231        duration.as_secs().serialize(serializer)
232    }
233
234    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
235    where
236        D: Deserializer<'de>,
237    {
238        let secs = u64::deserialize(deserializer)?;
239        Ok(Duration::from_secs(secs))
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    
247    #[test]
248    fn test_query_key_creation() {
249        let key = QueryKey::new(["users", "123"]);
250        assert_eq!(key.segments, vec!["users", "123"]);
251        
252        let key2 = QueryKey::from("single");
253        assert_eq!(key2.segments, vec!["single"]);
254    }
255    
256    #[test]
257    fn test_query_key_pattern_matching() {
258        let key = QueryKey::new(["users", "123", "profile"]);
259        
260        // Exact match
261        let exact_pattern = QueryKeyPattern::Exact(QueryKey::new(["users", "123", "profile"]));
262        assert!(key.matches_pattern(&exact_pattern));
263        
264        // Prefix match
265        let prefix_pattern = QueryKeyPattern::Prefix(QueryKey::new(["users"]));
266        assert!(key.matches_pattern(&prefix_pattern));
267        
268        // Contains match
269        let contains_pattern = QueryKeyPattern::Contains("123".to_string());
270        assert!(key.matches_pattern(&contains_pattern));
271    }
272    
273    #[test]
274    fn test_query_meta_stale_check() {
275        let mut meta = QueryMeta::default();
276        meta.stale_time = Duration::from_secs(60);
277        
278        // Should not be stale immediately
279        assert!(!meta.is_stale());
280        
281        // Should be stale after waiting
282        meta.updated_at = Instant::now() - Duration::from_secs(120);
283        assert!(meta.is_stale());
284    }
285}