1use std::fmt::{self, Display};
4use std::time::Instant;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct EntityTag {
9 value: String,
11}
12
13impl EntityTag {
14 pub fn new(value: impl Into<String>) -> Self {
16 Self {
17 value: value.into(),
18 }
19 }
20
21 pub fn entity(entity: &str) -> Self {
23 Self::new(format!("entity:{}", entity))
24 }
25
26 pub fn record<I: Display>(entity: &str, id: I) -> Self {
28 Self::new(format!("record:{}:{}", entity, id))
29 }
30
31 pub fn tenant(tenant: &str) -> Self {
33 Self::new(format!("tenant:{}", tenant))
34 }
35
36 pub fn query(name: &str) -> Self {
38 Self::new(format!("query:{}", name))
39 }
40
41 pub fn relation(from: &str, to: &str) -> Self {
43 Self::new(format!("rel:{}:{}", from, to))
44 }
45
46 pub fn value(&self) -> &str {
48 &self.value
49 }
50
51 pub fn matches(&self, pattern: &str) -> bool {
53 if pattern.contains('*') {
54 super::key::KeyPattern::new(pattern).matches_str(&self.value)
55 } else {
56 self.value == pattern
57 }
58 }
59}
60
61impl Display for EntityTag {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}", self.value)
64 }
65}
66
67impl From<&str> for EntityTag {
68 fn from(s: &str) -> Self {
69 Self::new(s)
70 }
71}
72
73impl From<String> for EntityTag {
74 fn from(s: String) -> Self {
75 Self::new(s)
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct InvalidationEvent {
82 pub event_type: InvalidationEventType,
84 pub entity: String,
86 pub record_id: Option<String>,
88 pub timestamp: Instant,
90 pub tags: Vec<EntityTag>,
92 pub metadata: Option<String>,
94}
95
96impl InvalidationEvent {
97 pub fn new(event_type: InvalidationEventType, entity: impl Into<String>) -> Self {
99 Self {
100 event_type,
101 entity: entity.into(),
102 record_id: None,
103 timestamp: Instant::now(),
104 tags: Vec::new(),
105 metadata: None,
106 }
107 }
108
109 pub fn insert(entity: impl Into<String>) -> Self {
111 Self::new(InvalidationEventType::Insert, entity)
112 }
113
114 pub fn update(entity: impl Into<String>) -> Self {
116 Self::new(InvalidationEventType::Update, entity)
117 }
118
119 pub fn delete(entity: impl Into<String>) -> Self {
121 Self::new(InvalidationEventType::Delete, entity)
122 }
123
124 pub fn with_record<I: Display>(mut self, id: I) -> Self {
126 self.record_id = Some(id.to_string());
127 self
128 }
129
130 pub fn with_tag(mut self, tag: impl Into<EntityTag>) -> Self {
132 self.tags.push(tag.into());
133 self
134 }
135
136 pub fn with_tags(mut self, tags: impl IntoIterator<Item = EntityTag>) -> Self {
138 self.tags.extend(tags);
139 self
140 }
141
142 pub fn with_metadata(mut self, metadata: impl Into<String>) -> Self {
144 self.metadata = Some(metadata.into());
145 self
146 }
147
148 pub fn all_tags(&self) -> Vec<EntityTag> {
150 let mut tags = self.tags.clone();
151
152 tags.push(EntityTag::entity(&self.entity));
154
155 if let Some(ref id) = self.record_id {
157 tags.push(EntityTag::record(&self.entity, id));
158 }
159
160 tags
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum InvalidationEventType {
167 Insert,
169 Update,
171 Delete,
173 Bulk,
175 SchemaChange,
177 Manual,
179}
180
181impl Display for InvalidationEventType {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 match self {
184 Self::Insert => write!(f, "insert"),
185 Self::Update => write!(f, "update"),
186 Self::Delete => write!(f, "delete"),
187 Self::Bulk => write!(f, "bulk"),
188 Self::SchemaChange => write!(f, "schema_change"),
189 Self::Manual => write!(f, "manual"),
190 }
191 }
192}
193
194#[derive(Debug, Clone)]
196pub enum InvalidationStrategy {
197 Immediate,
199
200 Delayed {
202 delay_ms: u64,
203 },
204
205 EventBased {
207 events: Vec<InvalidationEventType>,
208 },
209
210 TagBased {
212 tags: Vec<EntityTag>,
213 },
214
215 TtlOnly,
217
218 Custom {
220 name: String,
221 },
222}
223
224impl InvalidationStrategy {
225 pub fn immediate() -> Self {
227 Self::Immediate
228 }
229
230 pub fn delayed(delay_ms: u64) -> Self {
232 Self::Delayed { delay_ms }
233 }
234
235 pub fn on_events(events: Vec<InvalidationEventType>) -> Self {
237 Self::EventBased { events }
238 }
239
240 pub fn for_tags(tags: Vec<EntityTag>) -> Self {
242 Self::TagBased { tags }
243 }
244
245 pub fn ttl_only() -> Self {
247 Self::TtlOnly
248 }
249
250 pub fn should_invalidate(&self, event: &InvalidationEvent) -> bool {
252 match self {
253 Self::Immediate => true,
254 Self::Delayed { .. } => true, Self::EventBased { events } => events.contains(&event.event_type),
256 Self::TagBased { tags } => {
257 event.all_tags().iter().any(|t| tags.contains(t))
258 }
259 Self::TtlOnly => false,
260 Self::Custom { .. } => true, }
262 }
263}
264
265impl Default for InvalidationStrategy {
266 fn default() -> Self {
267 Self::Immediate
268 }
269}
270
271pub trait InvalidationHandler: Send + Sync + 'static {
273 fn handle(
275 &self,
276 event: &InvalidationEvent,
277 ) -> impl std::future::Future<Output = super::CacheResult<()>> + Send;
278}
279
280pub struct FnHandler<F>(pub F);
282
283impl<F, Fut> InvalidationHandler for FnHandler<F>
284where
285 F: Fn(&InvalidationEvent) -> Fut + Send + Sync + 'static,
286 Fut: std::future::Future<Output = super::CacheResult<()>> + Send,
287{
288 async fn handle(&self, event: &InvalidationEvent) -> super::CacheResult<()> {
289 (self.0)(event).await
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_entity_tag() {
299 let tag = EntityTag::entity("User");
300 assert_eq!(tag.value(), "entity:User");
301
302 let record_tag = EntityTag::record("User", 123);
303 assert_eq!(record_tag.value(), "record:User:123");
304 }
305
306 #[test]
307 fn test_invalidation_event() {
308 let event = InvalidationEvent::insert("User")
309 .with_record(123)
310 .with_tag("custom_tag");
311
312 assert_eq!(event.entity, "User");
313 assert_eq!(event.record_id, Some("123".to_string()));
314 assert_eq!(event.event_type, InvalidationEventType::Insert);
315
316 let tags = event.all_tags();
317 assert!(tags.iter().any(|t| t.value() == "entity:User"));
318 assert!(tags.iter().any(|t| t.value() == "record:User:123"));
319 }
320
321 #[test]
322 fn test_invalidation_strategy() {
323 let immediate = InvalidationStrategy::immediate();
324 let event = InvalidationEvent::update("User");
325 assert!(immediate.should_invalidate(&event));
326
327 let events_only = InvalidationStrategy::on_events(vec![InvalidationEventType::Delete]);
328 assert!(!events_only.should_invalidate(&event));
329
330 let delete_event = InvalidationEvent::delete("User");
331 assert!(events_only.should_invalidate(&delete_event));
332 }
333
334 #[test]
335 fn test_tag_matching() {
336 let tag = EntityTag::new("entity:User");
337 assert!(tag.matches("entity:User"));
338 assert!(tag.matches("entity:*"));
339 assert!(!tag.matches("entity:Post"));
340 }
341}
342
343