Skip to main content

oxcache/core/
events.rs

1// Copyright (c) 2025-2026 Kirky.X
2// SPDX-License-Identifier: MIT
3//! 缓存事件系统
4//!
5//! 提供缓存事件的发布和订阅机制,支持监控缓存操作、性能跟踪和自定义处理逻辑。
6
7use crate::error::OxCacheError;
8use async_trait::async_trait;
9use std::fmt;
10
11/// 缓存事件类型
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum CacheEventType {
14    /// 缓存命中
15    Hit,
16    /// 缓存未命中
17    Miss,
18    /// 缓存设置
19    Set,
20    /// 缓存删除
21    Delete,
22    /// 缓存过期
23    Expire,
24    /// 缓存清除
25    Clear,
26    /// 缓存获取(包含命中和未命中)
27    Get,
28    /// 批量操作开始
29    BatchStart,
30    /// 批量操作结束
31    BatchEnd,
32    /// 错误发生
33    Error,
34    /// 连接建立
35    Connect,
36    /// 连接断开
37    Disconnect,
38    /// 自定义事件
39    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/// 缓存事件
63#[derive(Debug, Clone)]
64pub struct CacheEvent {
65    /// 事件类型
66    pub event_type: CacheEventType,
67    /// 缓存键
68    pub key: Option<String>,
69    /// 事件时间戳(毫秒)
70    pub timestamp: u64,
71    /// 延迟(毫秒)
72    pub latency_ms: Option<u64>,
73    /// 错误信息(如果事件是错误)
74    pub error: Option<String>,
75    /// 额外数据
76    pub metadata: Vec<(String, String)>,
77}
78
79impl CacheEvent {
80    /// 创建新的缓存事件
81    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    /// 设置缓存键
93    pub fn with_key(mut self, key: impl Into<String>) -> Self {
94        self.key = Some(key.into());
95        self
96    }
97
98    /// 设置延迟
99    pub fn with_latency(mut self, latency_ms: u64) -> Self {
100        self.latency_ms = Some(latency_ms);
101        self
102    }
103
104    /// 设置错误
105    pub fn with_error(mut self, error: impl Into<String>) -> Self {
106        self.error = Some(error.into());
107        self
108    }
109
110    /// 添加元数据
111    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
117/// 获取当前时间戳(毫秒)
118///
119/// 若系统时钟早于 UNIX_EPOCH(嵌入式平台、时钟回拨),返回 0 而非 panic。
120fn 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/// 事件发布器 Trait
128///
129/// 用于发布缓存事件。
130/// 所有方法使用具体 `String` 类型(而非 `impl Into<String>`)以确保 dyn-compatible,
131/// 允许 `Arc<dyn EventPublisher>` 使用。
132#[async_trait]
133pub trait EventPublisher: Send + Sync {
134    /// 发布事件
135    async fn publish(&self, event: CacheEvent) -> Result<(), OxCacheError>;
136
137    /// 发布命中事件
138    fn publish_hit(&self, _key: String, _latency_ms: u64) -> Result<(), OxCacheError> {
139        Ok(())
140    }
141
142    /// 发布未命中事件
143    fn publish_miss(&self, _key: String, _latency_ms: u64) -> Result<(), OxCacheError> {
144        Ok(())
145    }
146
147    /// 发布设置事件
148    fn publish_set(&self, _key: String) -> Result<(), OxCacheError> {
149        Ok(())
150    }
151
152    /// 发布删除事件
153    fn publish_delete(&self, _key: String) -> Result<(), OxCacheError> {
154        Ok(())
155    }
156
157    /// 发布错误事件
158    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        // CacheEvent doesn't have with_value method, test with available methods
184        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        // Test all event types have proper Display implementation
214        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); // Only one metadata pair added
236        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    // ============================================================================
250    // Display 测试 - Set 和 Delete (lines 49-50)
251    // ============================================================================
252
253    #[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    // ============================================================================
264    // EventPublisher 默认方法测试 (lines 136-163)
265    // ============================================================================
266
267    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        // 测试 publish_hit 默认实现 (lines 136-139)
279        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        // 测试 publish_miss 默认实现 (lines 143-145)
287        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        // 测试 publish_set 默认实现 (lines 149-151)
295        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        // 测试 publish_delete 默认实现 (lines 155-157)
303        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        // 测试 publish_error 默认实现 (lines 161-163)
311        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        // 测试 publish 方法
326        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    // ============================================================================
333    // CacheEvent 额外测试
334    // ============================================================================
335
336    #[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}