1use crate::error::OxCacheError;
8use async_trait::async_trait;
9use std::fmt;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum CacheEventType {
14 Hit,
16 Miss,
18 Set,
20 Delete,
22 Expire,
24 Clear,
26 Get,
28 BatchStart,
30 BatchEnd,
32 Error,
34 Connect,
36 Disconnect,
38 Custom(String),
40}
41
42impl fmt::Display for CacheEventType {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 CacheEventType::Hit => write!(f, "hit"),
46 CacheEventType::Miss => write!(f, "miss"),
47 CacheEventType::Set => write!(f, "set"),
48 CacheEventType::Delete => write!(f, "delete"),
49 CacheEventType::Expire => write!(f, "expire"),
50 CacheEventType::Clear => write!(f, "clear"),
51 CacheEventType::Get => write!(f, "get"),
52 CacheEventType::BatchStart => write!(f, "batch_start"),
53 CacheEventType::BatchEnd => write!(f, "batch_end"),
54 CacheEventType::Error => write!(f, "error"),
55 CacheEventType::Connect => write!(f, "connect"),
56 CacheEventType::Disconnect => write!(f, "disconnect"),
57 CacheEventType::Custom(s) => write!(f, "custom:{}", s),
58 }
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct CacheEvent {
65 pub event_type: CacheEventType,
67 pub key: Option<String>,
69 pub timestamp: u64,
71 pub latency_ms: Option<u64>,
73 pub error: Option<String>,
75 pub metadata: Vec<(String, String)>,
77}
78
79impl CacheEvent {
80 pub fn new(event_type: CacheEventType) -> Self {
82 Self {
83 event_type,
84 key: None,
85 timestamp: current_timestamp_ms(),
86 latency_ms: None,
87 error: None,
88 metadata: Vec::new(),
89 }
90 }
91
92 pub fn with_key(mut self, key: impl Into<String>) -> Self {
94 self.key = Some(key.into());
95 self
96 }
97
98 pub fn with_latency(mut self, latency_ms: u64) -> Self {
100 self.latency_ms = Some(latency_ms);
101 self
102 }
103
104 pub fn with_error(mut self, error: impl Into<String>) -> Self {
106 self.error = Some(error.into());
107 self
108 }
109
110 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
112 self.metadata.push((key.into(), value.into()));
113 self
114 }
115}
116
117fn current_timestamp_ms() -> u64 {
121 std::time::SystemTime::now()
122 .duration_since(std::time::UNIX_EPOCH)
123 .unwrap_or(std::time::Duration::ZERO)
124 .as_millis() as u64
125}
126
127#[async_trait]
133pub trait EventPublisher: Send + Sync {
134 async fn publish(&self, event: CacheEvent) -> Result<(), OxCacheError>;
136
137 fn publish_hit(&self, _key: String, _latency_ms: u64) -> Result<(), OxCacheError> {
139 Ok(())
140 }
141
142 fn publish_miss(&self, _key: String, _latency_ms: u64) -> Result<(), OxCacheError> {
144 Ok(())
145 }
146
147 fn publish_set(&self, _key: String) -> Result<(), OxCacheError> {
149 Ok(())
150 }
151
152 fn publish_delete(&self, _key: String) -> Result<(), OxCacheError> {
154 Ok(())
155 }
156
157 fn publish_error(&self, _key: Option<String>, _error: String) -> Result<(), OxCacheError> {
159 Ok(())
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_cache_event_creation() {
169 let event = CacheEvent::new(CacheEventType::Hit).with_key("test_key");
170 assert_eq!(event.event_type, CacheEventType::Hit);
171 assert_eq!(event.key, Some("test_key".to_string()));
172 }
173
174 #[test]
175 fn test_cache_event_type_display() {
176 assert_eq!(CacheEventType::Hit.to_string(), "hit");
177 assert_eq!(CacheEventType::Miss.to_string(), "miss");
178 assert_eq!(CacheEventType::Custom("test".to_string()).to_string(), "custom:test");
179 }
180
181 #[test]
182 fn test_cache_event_with_value() {
183 let event = CacheEvent::new(CacheEventType::Set).with_key("user:1").with_latency(5);
185 assert_eq!(event.key, Some("user:1".to_string()));
186 assert_eq!(event.latency_ms, Some(5));
187 }
188
189 #[test]
190 fn test_cache_event_with_metadata() {
191 let event = CacheEvent::new(CacheEventType::Miss)
192 .with_key("missing_key")
193 .with_latency(5)
194 .with_metadata("test_node", "test_service");
195
196 assert_eq!(event.latency_ms, Some(5));
197 assert_eq!(event.metadata.len(), 1);
198 assert_eq!(event.metadata[0], ("test_node".to_string(), "test_service".to_string()));
199 }
200
201 #[test]
202 fn test_cache_event_error() {
203 let event = CacheEvent::new(CacheEventType::Error)
204 .with_key("error_key")
205 .with_error("Connection timeout");
206
207 assert_eq!(event.event_type, CacheEventType::Error);
208 assert_eq!(event.error, Some("Connection timeout".to_string()));
209 }
210
211 #[test]
212 fn test_cache_event_types_complete() {
213 assert_eq!(CacheEventType::Expire.to_string(), "expire");
215 assert_eq!(CacheEventType::Clear.to_string(), "clear");
216 assert_eq!(CacheEventType::Get.to_string(), "get");
217 assert_eq!(CacheEventType::BatchStart.to_string(), "batch_start");
218 assert_eq!(CacheEventType::BatchEnd.to_string(), "batch_end");
219 assert_eq!(CacheEventType::Error.to_string(), "error");
220 assert_eq!(CacheEventType::Connect.to_string(), "connect");
221 assert_eq!(CacheEventType::Disconnect.to_string(), "disconnect");
222 }
223
224 #[test]
225 fn test_cache_event_with_all_optional_fields() {
226 let event = CacheEvent::new(CacheEventType::Get)
227 .with_key("test_key")
228 .with_latency(100)
229 .with_metadata("node1", "service1")
230 .with_error("test error");
231
232 assert_eq!(event.event_type, CacheEventType::Get);
233 assert_eq!(event.key, Some("test_key".to_string()));
234 assert_eq!(event.latency_ms, Some(100));
235 assert_eq!(event.metadata.len(), 1); assert_eq!(event.error, Some("test error".to_string()));
237 }
238
239 #[test]
240 fn test_cache_event_clone() {
241 let event = CacheEvent::new(CacheEventType::Hit).with_key("key").with_latency(10);
242
243 let cloned = event.clone();
244 assert_eq!(event.event_type, cloned.event_type);
245 assert_eq!(event.key, cloned.key);
246 assert_eq!(event.latency_ms, cloned.latency_ms);
247 }
248
249 #[test]
254 fn test_cache_event_type_set_display() {
255 assert_eq!(CacheEventType::Set.to_string(), "set");
256 }
257
258 #[test]
259 fn test_cache_event_type_delete_display() {
260 assert_eq!(CacheEventType::Delete.to_string(), "delete");
261 }
262
263 struct NoopPublisher;
268
269 #[async_trait]
270 impl EventPublisher for NoopPublisher {
271 async fn publish(&self, _event: CacheEvent) -> Result<(), OxCacheError> {
272 Ok(())
273 }
274 }
275
276 #[tokio::test]
277 async fn test_event_publisher_publish_hit_default() {
278 let publisher = NoopPublisher;
280 let result = publisher.publish_hit("key1".to_string(), 10);
281 assert!(result.is_ok());
282 }
283
284 #[tokio::test]
285 async fn test_event_publisher_publish_miss_default() {
286 let publisher = NoopPublisher;
288 let result = publisher.publish_miss("key1".to_string(), 10);
289 assert!(result.is_ok());
290 }
291
292 #[tokio::test]
293 async fn test_event_publisher_publish_set_default() {
294 let publisher = NoopPublisher;
296 let result = publisher.publish_set("key1".to_string());
297 assert!(result.is_ok());
298 }
299
300 #[tokio::test]
301 async fn test_event_publisher_publish_delete_default() {
302 let publisher = NoopPublisher;
304 let result = publisher.publish_delete("key1".to_string());
305 assert!(result.is_ok());
306 }
307
308 #[tokio::test]
309 async fn test_event_publisher_publish_error_default() {
310 let publisher = NoopPublisher;
312 let result = publisher.publish_error(Some("key1".to_string()), "timeout".to_string());
313 assert!(result.is_ok());
314 }
315
316 #[tokio::test]
317 async fn test_event_publisher_publish_error_default_none_key() {
318 let publisher = NoopPublisher;
319 let result = publisher.publish_error(None, "connection failed".to_string());
320 assert!(result.is_ok());
321 }
322
323 #[tokio::test]
324 async fn test_event_publisher_publish() {
325 let publisher = NoopPublisher;
327 let event = CacheEvent::new(CacheEventType::Hit).with_key("key1");
328 let result = publisher.publish(event).await;
329 assert!(result.is_ok());
330 }
331
332 #[test]
337 fn test_cache_event_new_default_fields() {
338 let event = CacheEvent::new(CacheEventType::Set);
339 assert_eq!(event.event_type, CacheEventType::Set);
340 assert!(event.key.is_none());
341 assert!(event.latency_ms.is_none());
342 assert!(event.error.is_none());
343 assert!(event.metadata.is_empty());
344 assert!(event.timestamp > 0);
345 }
346
347 #[test]
348 fn test_cache_event_with_multiple_metadata() {
349 let event = CacheEvent::new(CacheEventType::Get)
350 .with_key("test_key")
351 .with_metadata("node", "node1")
352 .with_metadata("service", "service1")
353 .with_metadata("region", "us-east");
354
355 assert_eq!(event.metadata.len(), 3);
356 assert_eq!(event.metadata[0], ("node".to_string(), "node1".to_string()));
357 assert_eq!(event.metadata[1], ("service".to_string(), "service1".to_string()));
358 assert_eq!(event.metadata[2], ("region".to_string(), "us-east".to_string()));
359 }
360
361 #[test]
362 fn test_cache_event_type_equality() {
363 assert_eq!(CacheEventType::Hit, CacheEventType::Hit);
364 assert_ne!(CacheEventType::Hit, CacheEventType::Miss);
365 assert_eq!(
366 CacheEventType::Custom("test".to_string()),
367 CacheEventType::Custom("test".to_string())
368 );
369 assert_ne!(
370 CacheEventType::Custom("test1".to_string()),
371 CacheEventType::Custom("test2".to_string())
372 );
373 }
374
375 #[test]
376 fn test_cache_event_debug() {
377 let event = CacheEvent::new(CacheEventType::Hit).with_key("key");
378 let debug_str = format!("{:?}", event);
379 assert!(debug_str.contains("CacheEvent"));
380 assert!(debug_str.contains("Hit"));
381 }
382}