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, Default)]
196pub enum InvalidationStrategy {
197 #[default]
199 Immediate,
200
201 Delayed { delay_ms: u64 },
203
204 EventBased { events: Vec<InvalidationEventType> },
206
207 TagBased { tags: Vec<EntityTag> },
209
210 TtlOnly,
212
213 Custom { name: String },
215}
216
217impl InvalidationStrategy {
218 pub fn immediate() -> Self {
220 Self::Immediate
221 }
222
223 pub fn delayed(delay_ms: u64) -> Self {
225 Self::Delayed { delay_ms }
226 }
227
228 pub fn on_events(events: Vec<InvalidationEventType>) -> Self {
230 Self::EventBased { events }
231 }
232
233 pub fn for_tags(tags: Vec<EntityTag>) -> Self {
235 Self::TagBased { tags }
236 }
237
238 pub fn ttl_only() -> Self {
240 Self::TtlOnly
241 }
242
243 pub fn should_invalidate(&self, event: &InvalidationEvent) -> bool {
245 match self {
246 Self::Immediate => true,
247 Self::Delayed { .. } => true, Self::EventBased { events } => events.contains(&event.event_type),
249 Self::TagBased { tags } => event.all_tags().iter().any(|t| tags.contains(t)),
250 Self::TtlOnly => false,
251 Self::Custom { .. } => true, }
253 }
254}
255
256pub trait InvalidationHandler: Send + Sync + 'static {
258 fn handle(
260 &self,
261 event: &InvalidationEvent,
262 ) -> impl std::future::Future<Output = super::CacheResult<()>> + Send;
263}
264
265pub struct FnHandler<F>(pub F);
267
268impl<F, Fut> InvalidationHandler for FnHandler<F>
269where
270 F: Fn(&InvalidationEvent) -> Fut + Send + Sync + 'static,
271 Fut: std::future::Future<Output = super::CacheResult<()>> + Send,
272{
273 async fn handle(&self, event: &InvalidationEvent) -> super::CacheResult<()> {
274 (self.0)(event).await
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_entity_tag() {
284 let tag = EntityTag::entity("User");
285 assert_eq!(tag.value(), "entity:User");
286
287 let record_tag = EntityTag::record("User", 123);
288 assert_eq!(record_tag.value(), "record:User:123");
289 }
290
291 #[test]
292 fn test_invalidation_event() {
293 let event = InvalidationEvent::insert("User")
294 .with_record(123)
295 .with_tag("custom_tag");
296
297 assert_eq!(event.entity, "User");
298 assert_eq!(event.record_id, Some("123".to_string()));
299 assert_eq!(event.event_type, InvalidationEventType::Insert);
300
301 let tags = event.all_tags();
302 assert!(tags.iter().any(|t| t.value() == "entity:User"));
303 assert!(tags.iter().any(|t| t.value() == "record:User:123"));
304 }
305
306 #[test]
307 fn test_invalidation_strategy() {
308 let immediate = InvalidationStrategy::immediate();
309 let event = InvalidationEvent::update("User");
310 assert!(immediate.should_invalidate(&event));
311
312 let events_only = InvalidationStrategy::on_events(vec![InvalidationEventType::Delete]);
313 assert!(!events_only.should_invalidate(&event));
314
315 let delete_event = InvalidationEvent::delete("User");
316 assert!(events_only.should_invalidate(&delete_event));
317 }
318
319 #[test]
320 fn test_tag_matching() {
321 let tag = EntityTag::new("entity:User");
322 assert!(tag.matches("entity:User"));
323 assert!(tag.matches("entity:*"));
324 assert!(!tag.matches("entity:Post"));
325 }
326}