Skip to main content

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}