mqtt_topic_engine/cache_strategy.rs
1//! Cache strategy configuration for topic pattern matching
2//!
3//! Provides configuration options for caching topic matching results
4//! to improve performance for frequently used patterns.
5
6use std::num::NonZeroUsize;
7
8/// Strategy for caching topic matching results
9///
10/// Controls how topic match results are cached to optimize performance.
11/// Different strategies trade memory usage for lookup speed.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum CacheStrategy {
14 /// Use LRU (Least Recently Used) cache with a fixed size
15 ///
16 /// Maintains a cache of recently matched topics. When the cache is full,
17 /// the least recently used entry is evicted. Provides good performance
18 /// for workloads with repeated topic patterns.
19 ///
20 /// **Note:** This variant is only available with the `lru-cache` feature enabled.
21 ///
22 /// # Example
23 /// ```ignore
24 /// use std::num::NonZeroUsize;
25 /// use mqtt_topic_engine::CacheStrategy;
26 ///
27 /// let cache = CacheStrategy::Lru(NonZeroUsize::new(100).unwrap());
28 /// ```
29 #[cfg(feature = "lru-cache")]
30 Lru(NonZeroUsize),
31
32 /// No caching - always create new TopicPath instances
33 ///
34 /// Disables caching entirely. Use this when:
35 /// - Topic patterns are rarely repeated
36 /// - Memory is constrained
37 /// - Simplicity is preferred over performance
38 NoCache,
39}
40
41impl CacheStrategy {
42 /// Create a new cache strategy with the specified capacity
43 ///
44 /// Returns `NoCache` if capacity is 0, otherwise returns `Lru` with the given capacity
45 /// (if `lru-cache` feature is enabled).
46 ///
47 /// # Arguments
48 /// * `capacity` - Maximum number of entries to cache (0 means no caching)
49 ///
50 /// # Examples
51 /// ```
52 /// use mqtt_topic_engine::CacheStrategy;
53 ///
54 /// // Create no-cache strategy
55 /// let no_cache = CacheStrategy::new(0);
56 /// assert_eq!(no_cache, CacheStrategy::NoCache);
57 /// ```
58 ///
59 /// With `lru-cache` feature:
60 /// ```ignore
61 /// // Create LRU cache with 100 entries
62 /// let cache = CacheStrategy::new(100);
63 /// ```
64 pub fn new(capacity: usize) -> Self {
65 if capacity == 0 {
66 Self::NoCache
67 } else {
68 #[cfg(feature = "lru-cache")]
69 {
70 Self::Lru(
71 NonZeroUsize::new(capacity).expect("Capacity must be > 0"),
72 )
73 }
74 #[cfg(not(feature = "lru-cache"))]
75 {
76 tracing::warn!(
77 capacity,
78 "LRU cache requested with capacity {}, but 'lru-cache' \
79 feature is disabled. Falling back to NoCache. Enable \
80 'lru-cache' feature in Cargo.toml to use caching.",
81 capacity
82 );
83 Self::NoCache
84 }
85 }
86 }
87
88 /// Returns the cache capacity if using LRU strategy
89 ///
90 /// # Examples
91 /// ```
92 /// use mqtt_topic_engine::CacheStrategy;
93 ///
94 /// let no_cache = CacheStrategy::NoCache;
95 /// assert_eq!(no_cache.capacity(), None);
96 /// ```
97 ///
98 /// With `lru-cache` feature:
99 /// ```ignore
100 /// use std::num::NonZeroUsize;
101 /// use mqtt_topic_engine::CacheStrategy;
102 ///
103 /// let cache = CacheStrategy::new(100);
104 /// assert_eq!(cache.capacity(), Some(NonZeroUsize::new(100).unwrap()));
105 /// ```
106 pub fn capacity(&self) -> Option<NonZeroUsize> {
107 match self {
108 #[cfg(feature = "lru-cache")]
109 | Self::Lru(size) => Some(*size),
110 | Self::NoCache => None,
111 }
112 }
113}
114
115impl Default for CacheStrategy {
116 /// Default strategy is no caching
117 ///
118 /// This ensures predictable behavior and minimal memory usage by default.
119 fn default() -> Self {
120 Self::NoCache
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 #[cfg(feature = "lru-cache")]
130 fn test_new_with_capacity() {
131 let cache = CacheStrategy::new(100);
132 assert!(matches!(cache, CacheStrategy::Lru(_)));
133 assert_eq!(cache.capacity(), NonZeroUsize::new(100));
134 }
135
136 #[test]
137 #[cfg(not(feature = "lru-cache"))]
138 fn test_new_with_capacity_no_lru() {
139 // Without lru-cache feature, new(100) should return NoCache
140 let cache = CacheStrategy::new(100);
141 assert_eq!(cache, CacheStrategy::NoCache);
142 assert_eq!(cache.capacity(), None);
143 }
144
145 #[test]
146 fn test_new_with_zero_capacity() {
147 let cache = CacheStrategy::new(0);
148 assert_eq!(cache, CacheStrategy::NoCache);
149 assert_eq!(cache.capacity(), None);
150 }
151
152 #[test]
153 fn test_default() {
154 let cache = CacheStrategy::default();
155 assert_eq!(cache, CacheStrategy::NoCache);
156 }
157
158 #[test]
159 #[cfg(feature = "lru-cache")]
160 fn test_capacity() {
161 let lru = CacheStrategy::Lru(NonZeroUsize::new(50).unwrap());
162 assert_eq!(lru.capacity(), NonZeroUsize::new(50));
163
164 let no_cache = CacheStrategy::NoCache;
165 assert_eq!(no_cache.capacity(), None);
166 }
167
168 #[test]
169 fn test_capacity_no_cache() {
170 let no_cache = CacheStrategy::NoCache;
171 assert_eq!(no_cache.capacity(), None);
172 }
173
174 #[test]
175 fn test_clone_and_copy() {
176 let cache = CacheStrategy::new(100);
177 let cloned = cache;
178 assert_eq!(cache, cloned);
179 }
180}