1use std::time::SystemTime;
2
3use crate::CacheKey;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum CacheEventKind {
11 Hit,
13 Miss,
15 SingleFlightJoined,
17 LoadStarted,
19 LoadCompleted,
21 LoadFailed,
23 Stored,
25 Removed,
27 KeyInvalidated,
29 TagInvalidated,
31 Flushed,
33 StaleLoadDiscarded,
35 Expired,
37 Evicted,
39}
40
41impl CacheEventKind {
42 pub fn is_access(self) -> bool {
44 matches!(
45 self,
46 Self::Hit
47 | Self::Miss
48 | Self::SingleFlightJoined
49 | Self::LoadStarted
50 | Self::LoadCompleted
51 | Self::LoadFailed
52 )
53 }
54
55 pub fn is_mutation(self) -> bool {
57 !self.is_access()
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum CacheEventScope {
64 Key {
66 key: CacheKey<'static>,
68 },
69 Tag {
71 tag: String,
73 affected_keys: u64,
75 },
76 Cache {
78 affected_keys: Option<u64>,
80 },
81}
82
83impl CacheEventScope {
84 pub fn key(&self) -> Option<&str> {
86 match self {
87 Self::Key { key } => Some(key.as_str()),
88 Self::Tag { .. } | Self::Cache { .. } => None,
89 }
90 }
91
92 pub fn tag(&self) -> Option<&str> {
94 match self {
95 Self::Tag { tag, .. } => Some(tag),
96 Self::Key { .. } | Self::Cache { .. } => None,
97 }
98 }
99
100 pub fn affected_keys(&self) -> Option<u64> {
102 match self {
103 Self::Key { .. } => Some(1),
104 Self::Tag { affected_keys, .. } => Some(*affected_keys),
105 Self::Cache { affected_keys } => *affected_keys,
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112pub enum CacheEventOrigin {
113 LocalApi,
115 Loader,
117 SingleFlight,
119 Backend,
121 DistributedBus,
123}
124
125#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
131pub enum CacheEventValueMode {
132 #[default]
134 MetadataOnly,
135 EncodedBytes,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct CacheEvent {
142 kind: CacheEventKind,
143 scope: CacheEventScope,
144 origin: CacheEventOrigin,
145 tags: Vec<String>,
146 timestamp: SystemTime,
147}
148
149impl CacheEvent {
150 pub fn for_key<I, S>(
152 kind: CacheEventKind,
153 key: impl Into<String>,
154 origin: CacheEventOrigin,
155 tags: I,
156 ) -> Self
157 where
158 I: IntoIterator<Item = S>,
159 S: Into<String>,
160 {
161 Self {
162 kind,
163 scope: CacheEventScope::Key {
164 key: CacheKey::from(key.into()),
165 },
166 origin,
167 tags: tags.into_iter().map(Into::into).collect(),
168 timestamp: SystemTime::now(),
169 }
170 }
171
172 pub fn for_tag(
174 kind: CacheEventKind,
175 tag: impl Into<String>,
176 affected_keys: u64,
177 origin: CacheEventOrigin,
178 ) -> Self {
179 let tag = tag.into();
180 Self {
181 kind,
182 scope: CacheEventScope::Tag {
183 tag: tag.clone(),
184 affected_keys,
185 },
186 origin,
187 tags: vec![tag],
188 timestamp: SystemTime::now(),
189 }
190 }
191
192 pub fn for_cache(
194 kind: CacheEventKind,
195 affected_keys: Option<u64>,
196 origin: CacheEventOrigin,
197 ) -> Self {
198 Self {
199 kind,
200 scope: CacheEventScope::Cache { affected_keys },
201 origin,
202 tags: Vec::new(),
203 timestamp: SystemTime::now(),
204 }
205 }
206
207 pub fn kind(&self) -> CacheEventKind {
209 self.kind
210 }
211
212 pub fn scope(&self) -> &CacheEventScope {
214 &self.scope
215 }
216
217 pub fn origin(&self) -> CacheEventOrigin {
219 self.origin
220 }
221
222 pub fn key(&self) -> Option<&str> {
224 self.scope.key()
225 }
226
227 pub fn tag(&self) -> Option<&str> {
229 self.scope.tag()
230 }
231
232 pub fn tags(&self) -> &[String] {
234 &self.tags
235 }
236
237 pub fn affected_keys(&self) -> Option<u64> {
239 self.scope.affected_keys()
240 }
241
242 pub fn timestamp(&self) -> SystemTime {
244 self.timestamp
245 }
246}
247
248#[derive(Debug, Clone, Default, PartialEq, Eq)]
254pub struct CacheEventOptions {
255 include_kinds: Option<Vec<CacheEventKind>>,
256 exclude_kinds: Vec<CacheEventKind>,
257 key: Option<String>,
258 key_prefix: Option<String>,
259 tag: Option<String>,
260 origin: Option<CacheEventOrigin>,
261 value_mode: CacheEventValueMode,
262}
263
264impl CacheEventOptions {
265 pub fn new() -> Self {
267 Self::default()
268 }
269
270 pub fn mutations() -> Self {
272 Self::new().include_kinds([
273 CacheEventKind::Stored,
274 CacheEventKind::Removed,
275 CacheEventKind::KeyInvalidated,
276 CacheEventKind::TagInvalidated,
277 CacheEventKind::Flushed,
278 CacheEventKind::StaleLoadDiscarded,
279 CacheEventKind::Expired,
280 CacheEventKind::Evicted,
281 ])
282 }
283
284 pub fn access() -> Self {
286 Self::new().include_kinds([
287 CacheEventKind::Hit,
288 CacheEventKind::Miss,
289 CacheEventKind::SingleFlightJoined,
290 CacheEventKind::LoadStarted,
291 CacheEventKind::LoadCompleted,
292 CacheEventKind::LoadFailed,
293 ])
294 }
295
296 pub fn include_kind(self, kind: CacheEventKind) -> Self {
298 self.include_kinds([kind])
299 }
300
301 pub fn include_kinds<I>(mut self, kinds: I) -> Self
303 where
304 I: IntoIterator<Item = CacheEventKind>,
305 {
306 self.include_kinds
307 .get_or_insert_with(Vec::new)
308 .extend(kinds);
309 self
310 }
311
312 pub fn exclude_kind(self, kind: CacheEventKind) -> Self {
314 self.exclude_kinds([kind])
315 }
316
317 pub fn exclude_kinds<I>(mut self, kinds: I) -> Self
319 where
320 I: IntoIterator<Item = CacheEventKind>,
321 {
322 self.exclude_kinds.extend(kinds);
323 self
324 }
325
326 pub fn key(mut self, key: impl Into<String>) -> Self {
328 self.key = Some(key.into());
329 self
330 }
331
332 pub fn key_prefix(mut self, key_prefix: impl Into<String>) -> Self {
334 self.key_prefix = Some(key_prefix.into());
335 self
336 }
337
338 pub fn tag(mut self, tag: impl Into<String>) -> Self {
340 self.tag = Some(tag.into());
341 self
342 }
343
344 pub fn origin(mut self, origin: CacheEventOrigin) -> Self {
346 self.origin = Some(origin);
347 self
348 }
349
350 pub fn value_mode(mut self, value_mode: CacheEventValueMode) -> Self {
352 self.value_mode = value_mode;
353 self
354 }
355
356 pub fn value_mode_value(&self) -> CacheEventValueMode {
358 self.value_mode
359 }
360
361 pub fn matches(&self, event: &CacheEvent) -> bool {
363 if let Some(include_kinds) = &self.include_kinds {
364 if !include_kinds.contains(&event.kind()) {
365 return false;
366 }
367 }
368
369 if self.exclude_kinds.contains(&event.kind()) {
370 return false;
371 }
372
373 if let Some(key) = &self.key {
374 if event.key() != Some(key.as_str()) {
375 return false;
376 }
377 }
378
379 if let Some(key_prefix) = &self.key_prefix {
380 let Some(key) = event.key() else {
381 return false;
382 };
383 if !key.starts_with(key_prefix) {
384 return false;
385 }
386 }
387
388 if let Some(tag) = &self.tag {
389 let scope_matches = event.tag() == Some(tag.as_str());
390 let tags_match = event.tags().iter().any(|event_tag| event_tag == tag);
391 if !scope_matches && !tags_match {
392 return false;
393 }
394 }
395
396 if let Some(origin) = self.origin {
397 if event.origin() != origin {
398 return false;
399 }
400 }
401
402 true
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::{
409 CacheEvent, CacheEventKind, CacheEventOptions, CacheEventOrigin, CacheEventScope,
410 CacheEventValueMode,
411 };
412
413 #[test]
414 fn event_kind_groups_access_and_mutation_events() {
415 assert!(CacheEventKind::Hit.is_access());
416 assert!(CacheEventKind::LoadFailed.is_access());
417 assert!(!CacheEventKind::Stored.is_access());
418 assert!(CacheEventKind::Stored.is_mutation());
419 }
420
421 #[test]
422 fn event_constructors_capture_scope_tags_and_origin() {
423 let key_event = CacheEvent::for_key(
424 CacheEventKind::Stored,
425 "user:42",
426 CacheEventOrigin::LocalApi,
427 ["users", "user:42"],
428 );
429 let tag_event = CacheEvent::for_tag(
430 CacheEventKind::TagInvalidated,
431 "users",
432 3,
433 CacheEventOrigin::LocalApi,
434 );
435 let cache_event = CacheEvent::for_cache(
436 CacheEventKind::Flushed,
437 Some(10),
438 CacheEventOrigin::LocalApi,
439 );
440
441 assert_eq!(key_event.key(), Some("user:42"));
442 assert_eq!(
443 key_event.tags(),
444 &["users".to_owned(), "user:42".to_owned()]
445 );
446 assert_eq!(key_event.origin(), CacheEventOrigin::LocalApi);
447 assert_eq!(tag_event.tag(), Some("users"));
448 assert_eq!(tag_event.affected_keys(), Some(3));
449 assert_eq!(
450 cache_event.scope(),
451 &CacheEventScope::Cache {
452 affected_keys: Some(10)
453 }
454 );
455 }
456
457 #[test]
458 fn event_options_filter_by_kind_key_prefix_tag_and_origin() {
459 let stored = CacheEvent::for_key(
460 CacheEventKind::Stored,
461 "users:42",
462 CacheEventOrigin::LocalApi,
463 ["users"],
464 );
465 let removed = CacheEvent::for_key(
466 CacheEventKind::Removed,
467 "orders:7",
468 CacheEventOrigin::LocalApi,
469 ["orders"],
470 );
471
472 let options = CacheEventOptions::new()
473 .include_kind(CacheEventKind::Stored)
474 .key_prefix("users:")
475 .tag("users")
476 .origin(CacheEventOrigin::LocalApi);
477
478 assert!(options.matches(&stored));
479 assert!(!options.matches(&removed));
480 assert!(!options
481 .clone()
482 .exclude_kind(CacheEventKind::Stored)
483 .matches(&stored));
484 }
485
486 #[test]
487 fn event_options_mutations_and_access_presets_are_distinct() {
488 let stored = CacheEvent::for_key(
489 CacheEventKind::Stored,
490 "k",
491 CacheEventOrigin::LocalApi,
492 ["t"],
493 );
494 let hit = CacheEvent::for_key(CacheEventKind::Hit, "k", CacheEventOrigin::LocalApi, ["t"]);
495
496 assert!(CacheEventOptions::mutations().matches(&stored));
497 assert!(!CacheEventOptions::mutations().matches(&hit));
498 assert!(CacheEventOptions::access().matches(&hit));
499 assert!(!CacheEventOptions::access().matches(&stored));
500 }
501
502 #[test]
503 fn event_options_keep_requested_value_mode() {
504 let options = CacheEventOptions::new().value_mode(CacheEventValueMode::EncodedBytes);
505
506 assert_eq!(
507 options.value_mode_value(),
508 CacheEventValueMode::EncodedBytes
509 );
510 }
511}