1use std::fmt::{self, Display, Write};
4use std::hash::{Hash, Hasher};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct CacheKey {
12 prefix: String,
14 namespace: String,
16 identifier: String,
18 tenant: Option<String>,
20}
21
22impl CacheKey {
23 pub fn new(namespace: impl Into<String>, identifier: impl Into<String>) -> Self {
25 Self {
26 prefix: "prax".to_string(),
27 namespace: namespace.into(),
28 identifier: identifier.into(),
29 tenant: None,
30 }
31 }
32
33 pub fn with_prefix(
35 prefix: impl Into<String>,
36 namespace: impl Into<String>,
37 identifier: impl Into<String>,
38 ) -> Self {
39 Self {
40 prefix: prefix.into(),
41 namespace: namespace.into(),
42 identifier: identifier.into(),
43 tenant: None,
44 }
45 }
46
47 pub fn entity_record<I: Display>(entity: &str, id: I) -> Self {
49 Self::new(entity, format!("id:{}", id))
50 }
51
52 pub fn query(entity: &str, query_hash: u64) -> Self {
54 Self::new(entity, format!("query:{:x}", query_hash))
55 }
56
57 pub fn find_unique<I: Display>(entity: &str, field: &str, value: I) -> Self {
59 Self::new(entity, format!("unique:{}:{}", field, value))
60 }
61
62 pub fn find_many(entity: &str, filter_hash: u64) -> Self {
64 Self::new(entity, format!("many:{:x}", filter_hash))
65 }
66
67 pub fn aggregate(entity: &str, agg_hash: u64) -> Self {
69 Self::new(entity, format!("agg:{:x}", agg_hash))
70 }
71
72 pub fn relation<I: Display>(from_entity: &str, from_id: I, relation: &str) -> Self {
74 Self::new(from_entity, format!("rel:{}:{}:{}", from_id, relation, ""))
75 }
76
77 pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
79 self.tenant = Some(tenant.into());
80 self
81 }
82
83 pub fn as_str(&self) -> String {
85 let mut key = String::with_capacity(64);
86 key.push_str(&self.prefix);
87 key.push(':');
88
89 if let Some(ref tenant) = self.tenant {
90 key.push_str(tenant);
91 key.push(':');
92 }
93
94 key.push_str(&self.namespace);
95 key.push(':');
96 key.push_str(&self.identifier);
97 key
98 }
99
100 pub fn namespace(&self) -> &str {
102 &self.namespace
103 }
104
105 pub fn identifier(&self) -> &str {
107 &self.identifier
108 }
109
110 pub fn prefix(&self) -> &str {
112 &self.prefix
113 }
114
115 pub fn tenant(&self) -> Option<&str> {
117 self.tenant.as_deref()
118 }
119}
120
121impl Display for CacheKey {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{}", self.as_str())
124 }
125}
126
127impl Hash for CacheKey {
128 fn hash<H: Hasher>(&self, state: &mut H) {
129 self.prefix.hash(state);
130 self.namespace.hash(state);
131 self.identifier.hash(state);
132 self.tenant.hash(state);
133 }
134}
135
136impl From<&str> for CacheKey {
137 fn from(s: &str) -> Self {
138 let parts: Vec<&str> = s.split(':').collect();
140 match parts.len() {
141 2 => Self::new(parts[0], parts[1]),
142 3 => Self::with_prefix(parts[0], parts[1], parts[2]),
143 _ => Self::new("default", s),
144 }
145 }
146}
147
148impl From<String> for CacheKey {
149 fn from(s: String) -> Self {
150 Self::from(s.as_str())
151 }
152}
153
154#[derive(Debug, Default)]
156pub struct CacheKeyBuilder {
157 prefix: Option<String>,
158 namespace: Option<String>,
159 tenant: Option<String>,
160 parts: Vec<String>,
161}
162
163impl CacheKeyBuilder {
164 pub fn new() -> Self {
166 Self::default()
167 }
168
169 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
171 self.prefix = Some(prefix.into());
172 self
173 }
174
175 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
177 self.namespace = Some(namespace.into());
178 self
179 }
180
181 pub fn tenant(mut self, tenant: impl Into<String>) -> Self {
183 self.tenant = Some(tenant.into());
184 self
185 }
186
187 pub fn part(mut self, part: impl Into<String>) -> Self {
189 self.parts.push(part.into());
190 self
191 }
192
193 pub fn field<V: Display>(mut self, name: &str, value: V) -> Self {
195 self.parts.push(format!("{}:{}", name, value));
196 self
197 }
198
199 pub fn id<I: Display>(mut self, id: I) -> Self {
201 self.parts.push(format!("id:{}", id));
202 self
203 }
204
205 pub fn hash(mut self, hash: u64) -> Self {
207 self.parts.push(format!("{:x}", hash));
208 self
209 }
210
211 pub fn build(self) -> CacheKey {
213 let namespace = self.namespace.unwrap_or_else(|| "default".to_string());
214 let identifier = if self.parts.is_empty() {
215 "default".to_string()
216 } else {
217 self.parts.join(":")
218 };
219
220 let mut key = if let Some(prefix) = self.prefix {
221 CacheKey::with_prefix(prefix, namespace, identifier)
222 } else {
223 CacheKey::new(namespace, identifier)
224 };
225
226 if let Some(tenant) = self.tenant {
227 key = key.with_tenant(tenant);
228 }
229
230 key
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct KeyPattern {
239 pattern: String,
240}
241
242impl KeyPattern {
243 pub fn new(pattern: impl Into<String>) -> Self {
245 Self {
246 pattern: pattern.into(),
247 }
248 }
249
250 pub fn entity(entity: &str) -> Self {
252 Self::new(format!("prax:{}:*", entity))
253 }
254
255 pub fn record<I: Display>(entity: &str, id: I) -> Self {
257 Self::new(format!("prax:{}:*{}*", entity, id))
258 }
259
260 pub fn tenant(tenant: &str) -> Self {
262 Self::new(format!("prax:{}:*", tenant))
263 }
264
265 pub fn all() -> Self {
267 Self::new("prax:*")
268 }
269
270 pub fn with_prefix(prefix: &str, pattern: &str) -> Self {
272 Self::new(format!("{}:{}", prefix, pattern))
273 }
274
275 pub fn as_str(&self) -> &str {
277 &self.pattern
278 }
279
280 pub fn matches(&self, key: &CacheKey) -> bool {
282 self.matches_str(&key.as_str())
283 }
284
285 pub fn matches_str(&self, key: &str) -> bool {
287 glob_match(&self.pattern, key)
288 }
289
290 pub fn to_redis_pattern(&self) -> String {
292 self.pattern.clone()
293 }
294}
295
296impl Display for KeyPattern {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 write!(f, "{}", self.pattern)
299 }
300}
301
302fn glob_match(pattern: &str, text: &str) -> bool {
304 let mut pattern_chars = pattern.chars().peekable();
305 let mut text_chars = text.chars().peekable();
306
307 while let Some(p) = pattern_chars.next() {
308 match p {
309 '*' => {
310 if pattern_chars.peek().is_none() {
312 return true; }
314
315 let remaining_pattern: String = pattern_chars.collect();
317 let remaining_text: String = text_chars.collect();
318
319 for i in 0..=remaining_text.len() {
320 if glob_match(&remaining_pattern, &remaining_text[i..]) {
321 return true;
322 }
323 }
324 return false;
325 }
326 '?' => {
327 if text_chars.next().is_none() {
329 return false;
330 }
331 }
332 c => {
333 match text_chars.next() {
335 Some(t) if t == c => {}
336 _ => return false,
337 }
338 }
339 }
340 }
341
342 text_chars.next().is_none()
343}
344
345pub fn compute_hash<T: Hash>(value: &T) -> u64 {
347 use std::collections::hash_map::DefaultHasher;
348 let mut hasher = DefaultHasher::new();
349 value.hash(&mut hasher);
350 hasher.finish()
351}
352
353pub fn compute_hash_many<T: Hash>(values: &[T]) -> u64 {
355 use std::collections::hash_map::DefaultHasher;
356 let mut hasher = DefaultHasher::new();
357 for value in values {
358 value.hash(&mut hasher);
359 }
360 hasher.finish()
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_cache_key_creation() {
369 let key = CacheKey::new("User", "id:123");
370 assert_eq!(key.as_str(), "prax:User:id:123");
371 }
372
373 #[test]
374 fn test_cache_key_with_tenant() {
375 let key = CacheKey::new("User", "id:123").with_tenant("tenant-1");
376 assert_eq!(key.as_str(), "prax:tenant-1:User:id:123");
377 }
378
379 #[test]
380 fn test_entity_record_key() {
381 let key = CacheKey::entity_record("User", 42);
382 assert_eq!(key.as_str(), "prax:User:id:42");
383 }
384
385 #[test]
386 fn test_find_unique_key() {
387 let key = CacheKey::find_unique("User", "email", "test@example.com");
388 assert_eq!(key.as_str(), "prax:User:unique:email:test@example.com");
389 }
390
391 #[test]
392 fn test_key_builder() {
393 let key = CacheKeyBuilder::new()
394 .namespace("User")
395 .field("status", "active")
396 .id(123)
397 .build();
398
399 assert!(key.as_str().contains("User"));
400 assert!(key.as_str().contains("status:active"));
401 }
402
403 #[test]
404 fn test_key_pattern_entity() {
405 let pattern = KeyPattern::entity("User");
406 assert_eq!(pattern.as_str(), "prax:User:*");
407
408 let key1 = CacheKey::entity_record("User", 1);
409 let key2 = CacheKey::entity_record("Post", 1);
410
411 assert!(pattern.matches(&key1));
412 assert!(!pattern.matches(&key2));
413 }
414
415 #[test]
416 fn test_glob_matching() {
417 assert!(glob_match("*", "anything"));
418 assert!(glob_match("prax:*", "prax:User:123"));
419 assert!(glob_match("prax:User:*", "prax:User:id:123"));
420 assert!(!glob_match("prax:Post:*", "prax:User:id:123"));
421 assert!(glob_match("*:User:*", "prax:User:id:123"));
422 }
423
424 #[test]
425 fn test_compute_hash() {
426 let hash1 = compute_hash(&"test");
427 let hash2 = compute_hash(&"test");
428 let hash3 = compute_hash(&"other");
429
430 assert_eq!(hash1, hash2);
431 assert_ne!(hash1, hash3);
432 }
433}