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>(
74 from_entity: &str,
75 from_id: I,
76 relation: &str,
77 ) -> Self {
78 Self::new(from_entity, format!("rel:{}:{}:{}", from_id, relation, ""))
79 }
80
81 pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
83 self.tenant = Some(tenant.into());
84 self
85 }
86
87 pub fn as_str(&self) -> String {
89 let mut key = String::with_capacity(64);
90 key.push_str(&self.prefix);
91 key.push(':');
92
93 if let Some(ref tenant) = self.tenant {
94 key.push_str(tenant);
95 key.push(':');
96 }
97
98 key.push_str(&self.namespace);
99 key.push(':');
100 key.push_str(&self.identifier);
101 key
102 }
103
104 pub fn namespace(&self) -> &str {
106 &self.namespace
107 }
108
109 pub fn identifier(&self) -> &str {
111 &self.identifier
112 }
113
114 pub fn prefix(&self) -> &str {
116 &self.prefix
117 }
118
119 pub fn tenant(&self) -> Option<&str> {
121 self.tenant.as_deref()
122 }
123}
124
125impl Display for CacheKey {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 write!(f, "{}", self.as_str())
128 }
129}
130
131impl Hash for CacheKey {
132 fn hash<H: Hasher>(&self, state: &mut H) {
133 self.prefix.hash(state);
134 self.namespace.hash(state);
135 self.identifier.hash(state);
136 self.tenant.hash(state);
137 }
138}
139
140impl From<&str> for CacheKey {
141 fn from(s: &str) -> Self {
142 let parts: Vec<&str> = s.split(':').collect();
144 match parts.len() {
145 2 => Self::new(parts[0], parts[1]),
146 3 => Self::with_prefix(parts[0], parts[1], parts[2]),
147 _ => Self::new("default", s),
148 }
149 }
150}
151
152impl From<String> for CacheKey {
153 fn from(s: String) -> Self {
154 Self::from(s.as_str())
155 }
156}
157
158#[derive(Debug, Default)]
160pub struct CacheKeyBuilder {
161 prefix: Option<String>,
162 namespace: Option<String>,
163 tenant: Option<String>,
164 parts: Vec<String>,
165}
166
167impl CacheKeyBuilder {
168 pub fn new() -> Self {
170 Self::default()
171 }
172
173 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
175 self.prefix = Some(prefix.into());
176 self
177 }
178
179 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
181 self.namespace = Some(namespace.into());
182 self
183 }
184
185 pub fn tenant(mut self, tenant: impl Into<String>) -> Self {
187 self.tenant = Some(tenant.into());
188 self
189 }
190
191 pub fn part(mut self, part: impl Into<String>) -> Self {
193 self.parts.push(part.into());
194 self
195 }
196
197 pub fn field<V: Display>(mut self, name: &str, value: V) -> Self {
199 self.parts.push(format!("{}:{}", name, value));
200 self
201 }
202
203 pub fn id<I: Display>(mut self, id: I) -> Self {
205 self.parts.push(format!("id:{}", id));
206 self
207 }
208
209 pub fn hash(mut self, hash: u64) -> Self {
211 self.parts.push(format!("{:x}", hash));
212 self
213 }
214
215 pub fn build(self) -> CacheKey {
217 let namespace = self.namespace.unwrap_or_else(|| "default".to_string());
218 let identifier = if self.parts.is_empty() {
219 "default".to_string()
220 } else {
221 self.parts.join(":")
222 };
223
224 let mut key = if let Some(prefix) = self.prefix {
225 CacheKey::with_prefix(prefix, namespace, identifier)
226 } else {
227 CacheKey::new(namespace, identifier)
228 };
229
230 if let Some(tenant) = self.tenant {
231 key = key.with_tenant(tenant);
232 }
233
234 key
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct KeyPattern {
243 pattern: String,
244}
245
246impl KeyPattern {
247 pub fn new(pattern: impl Into<String>) -> Self {
249 Self {
250 pattern: pattern.into(),
251 }
252 }
253
254 pub fn entity(entity: &str) -> Self {
256 Self::new(format!("prax:{}:*", entity))
257 }
258
259 pub fn record<I: Display>(entity: &str, id: I) -> Self {
261 Self::new(format!("prax:{}:*{}*", entity, id))
262 }
263
264 pub fn tenant(tenant: &str) -> Self {
266 Self::new(format!("prax:{}:*", tenant))
267 }
268
269 pub fn all() -> Self {
271 Self::new("prax:*")
272 }
273
274 pub fn with_prefix(prefix: &str, pattern: &str) -> Self {
276 Self::new(format!("{}:{}", prefix, pattern))
277 }
278
279 pub fn as_str(&self) -> &str {
281 &self.pattern
282 }
283
284 pub fn matches(&self, key: &CacheKey) -> bool {
286 self.matches_str(&key.as_str())
287 }
288
289 pub fn matches_str(&self, key: &str) -> bool {
291 glob_match(&self.pattern, key)
292 }
293
294 pub fn to_redis_pattern(&self) -> String {
296 self.pattern.clone()
297 }
298}
299
300impl Display for KeyPattern {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(f, "{}", self.pattern)
303 }
304}
305
306fn glob_match(pattern: &str, text: &str) -> bool {
308 let mut pattern_chars = pattern.chars().peekable();
309 let mut text_chars = text.chars().peekable();
310
311 while let Some(p) = pattern_chars.next() {
312 match p {
313 '*' => {
314 if pattern_chars.peek().is_none() {
316 return true; }
318
319 let remaining_pattern: String = pattern_chars.collect();
321 let remaining_text: String = text_chars.collect();
322
323 for i in 0..=remaining_text.len() {
324 if glob_match(&remaining_pattern, &remaining_text[i..]) {
325 return true;
326 }
327 }
328 return false;
329 }
330 '?' => {
331 if text_chars.next().is_none() {
333 return false;
334 }
335 }
336 c => {
337 match text_chars.next() {
339 Some(t) if t == c => {}
340 _ => return false,
341 }
342 }
343 }
344 }
345
346 text_chars.next().is_none()
347}
348
349pub fn compute_hash<T: Hash>(value: &T) -> u64 {
351 use std::collections::hash_map::DefaultHasher;
352 let mut hasher = DefaultHasher::new();
353 value.hash(&mut hasher);
354 hasher.finish()
355}
356
357pub fn compute_hash_many<T: Hash>(values: &[T]) -> u64 {
359 use std::collections::hash_map::DefaultHasher;
360 let mut hasher = DefaultHasher::new();
361 for value in values {
362 value.hash(&mut hasher);
363 }
364 hasher.finish()
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_cache_key_creation() {
373 let key = CacheKey::new("User", "id:123");
374 assert_eq!(key.as_str(), "prax:User:id:123");
375 }
376
377 #[test]
378 fn test_cache_key_with_tenant() {
379 let key = CacheKey::new("User", "id:123").with_tenant("tenant-1");
380 assert_eq!(key.as_str(), "prax:tenant-1:User:id:123");
381 }
382
383 #[test]
384 fn test_entity_record_key() {
385 let key = CacheKey::entity_record("User", 42);
386 assert_eq!(key.as_str(), "prax:User:id:42");
387 }
388
389 #[test]
390 fn test_find_unique_key() {
391 let key = CacheKey::find_unique("User", "email", "test@example.com");
392 assert_eq!(key.as_str(), "prax:User:unique:email:test@example.com");
393 }
394
395 #[test]
396 fn test_key_builder() {
397 let key = CacheKeyBuilder::new()
398 .namespace("User")
399 .field("status", "active")
400 .id(123)
401 .build();
402
403 assert!(key.as_str().contains("User"));
404 assert!(key.as_str().contains("status:active"));
405 }
406
407 #[test]
408 fn test_key_pattern_entity() {
409 let pattern = KeyPattern::entity("User");
410 assert_eq!(pattern.as_str(), "prax:User:*");
411
412 let key1 = CacheKey::entity_record("User", 1);
413 let key2 = CacheKey::entity_record("Post", 1);
414
415 assert!(pattern.matches(&key1));
416 assert!(!pattern.matches(&key2));
417 }
418
419 #[test]
420 fn test_glob_matching() {
421 assert!(glob_match("*", "anything"));
422 assert!(glob_match("prax:*", "prax:User:123"));
423 assert!(glob_match("prax:User:*", "prax:User:id:123"));
424 assert!(!glob_match("prax:Post:*", "prax:User:id:123"));
425 assert!(glob_match("*:User:*", "prax:User:id:123"));
426 }
427
428 #[test]
429 fn test_compute_hash() {
430 let hash1 = compute_hash(&"test");
431 let hash2 = compute_hash(&"test");
432 let hash3 = compute_hash(&"other");
433
434 assert_eq!(hash1, hash2);
435 assert_ne!(hash1, hash3);
436 }
437}
438
439