mqtt_topic_engine/
topic_matcher.rs1#![allow(clippy::missing_docs_in_private_items)]
9use std::collections::{HashMap, HashSet};
10
11use arcstr::Substr;
12use thiserror::Error;
13
14use crate::topic_match::TopicPath;
15use crate::topic_pattern_item::TopicPatternItem;
16use crate::topic_pattern_path::TopicPatternPath;
17
18#[derive(Error, Debug, Clone, PartialEq, Eq)]
20pub enum TopicMatcherError {
21 #[error("Topic path cannot be empty for matching")]
23 EmptyTopicPath,
24
25 #[error("Invalid topic segment '{segment}' at position {position}")]
27 InvalidSegment {
28 segment: String,
30 position: usize,
32 },
33
34 #[error("Topic path contains invalid UTF-8: {details}")]
36 InvalidUtf8 {
37 details: String,
39 },
40}
41
42impl TopicMatcherError {
43 pub fn invalid_segment(
45 segment: impl Into<String>,
46 position: usize,
47 ) -> Self {
48 Self::InvalidSegment {
49 segment: segment.into(),
50 position,
51 }
52 }
53
54 pub fn invalid_utf8(details: impl Into<String>) -> Self {
56 Self::InvalidUtf8 {
57 details: details.into(),
58 }
59 }
60}
61
62#[derive(Debug)]
65pub struct TopicMatcherNode<T> {
66 exact_match_data: Option<T>,
68
69 exact_children: HashMap<Substr, TopicMatcherNode<T>>,
71
72 single_level_wildcard_node: Option<Box<TopicMatcherNode<T>>>,
74
75 multi_level_wildcard_data: Option<T>,
77}
78
79pub trait Len {
82 fn len(&self) -> usize;
84 fn is_empty(&self) -> bool {
86 self.len() == 0
87 }
88}
89
90impl<T> Len for HashSet<T> {
91 fn len(&self) -> usize {
92 self.len()
93 }
94 fn is_empty(&self) -> bool {
95 self.is_empty()
96 }
97}
98
99impl<K, V> Len for HashMap<K, V> {
100 fn len(&self) -> usize {
101 self.len()
102 }
103 fn is_empty(&self) -> bool {
104 self.is_empty()
105 }
106}
107
108impl<T: Default + Len> Default for TopicMatcherNode<T> {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl<T: Default + Len> TopicMatcherNode<T> {
115 pub fn new() -> Self {
117 Self {
118 exact_match_data: None,
119 exact_children: HashMap::new(),
120 single_level_wildcard_node: None,
121 multi_level_wildcard_data: None,
122 }
123 }
124
125 pub fn is_empty(&self) -> bool {
128 self.exact_match_data.as_ref().is_none_or(T::is_empty)
129 && self.exact_children.is_empty()
130 && self.single_level_wildcard_node.is_none()
131 && self
132 .multi_level_wildcard_data
133 .as_ref()
134 .is_none_or(T::is_empty)
135 }
136 pub fn get_or_create_subscription_table(
138 &mut self,
139 topic_path: &TopicPatternPath,
140 ) -> &mut T {
141 let mut current_node = self;
142
143 let resolved_segments = topic_path.resolve_bound_segments();
144 for segment in resolved_segments {
145 match segment {
146 | TopicPatternItem::Str(s) => {
147 current_node = current_node
148 .exact_children
149 .entry(s.clone())
150 .or_default()
151 }
152 | TopicPatternItem::Plus(_) => {
153 current_node = current_node
154 .single_level_wildcard_node
155 .get_or_insert_with(
156 || Box::new(TopicMatcherNode::new()),
157 )
158 }
159 | TopicPatternItem::Hash(_) => {
160 return current_node
162 .multi_level_wildcard_data
163 .get_or_insert_with(T::default);
164 }
165 }
166 }
167 current_node.exact_match_data.get_or_insert_with(T::default)
168 }
169
170 pub fn update_node<F>(
172 &mut self,
173 topic_path: &[TopicPatternItem],
174 mut f: F,
175 ) -> Result<bool, TopicMatcherError>
176 where
177 F: FnMut(&mut T),
178 {
179 if topic_path.is_empty() {
180 let data = self.exact_match_data.as_mut().ok_or_else(|| {
181 TopicMatcherError::invalid_segment(
182 "no_data_for_empty_path".to_string(),
183 0,
184 )
185 })?;
186 f(data);
187 if data.is_empty() {
188 self.exact_match_data = None
189 }
190 return Ok(self.is_empty());
191 }
192 let current_segment = &topic_path[0];
193 let rest_segments = &topic_path[1 ..];
194
195 match current_segment {
196 | TopicPatternItem::Str(s) => {
197 let child_node =
198 self.exact_children.get_mut(s).ok_or_else(|| {
199 TopicMatcherError::invalid_segment(s.as_str(), 0)
200 })?;
201 if child_node.update_node(rest_segments, f)? {
202 self.exact_children.remove(s);
203 return Ok(self.is_empty());
204 }
205 }
206 | TopicPatternItem::Plus(_) => {
207 let child_node = self
208 .single_level_wildcard_node
209 .as_mut()
210 .ok_or_else(|| {
211 TopicMatcherError::invalid_segment("+".to_string(), 0)
212 })?;
213 if child_node.update_node(rest_segments, f)? {
214 self.single_level_wildcard_node = None;
215 return Ok(self.is_empty());
216 }
217 }
218 | TopicPatternItem::Hash(_) => {
219 let hash_wildcard_data = self
220 .multi_level_wildcard_data
221 .as_mut()
222 .ok_or_else(|| {
223 TopicMatcherError::invalid_segment("#".to_string(), 0)
224 })?;
225 f(hash_wildcard_data);
226 if hash_wildcard_data.is_empty() {
227 self.multi_level_wildcard_data = None;
228 return Ok(self.is_empty());
229 }
230 }
231 }
232 Ok(false)
233 }
234
235 fn collect_matching_subscriptions<'a>(
237 &'a self,
238 topic: &[Substr],
239 matching_data: &mut Vec<&'a T>,
240 ) {
241 match topic {
242 | [] => {
243 self.exact_match_data
245 .iter()
246 .for_each(|data| matching_data.push(data));
247 self.multi_level_wildcard_data
248 .iter()
249 .for_each(|data| matching_data.push(data))
250 }
251 | [segment, remaining_segments @ ..] => {
252 if let Some(child) = self.exact_children.get(segment) {
254 child.collect_matching_subscriptions(
255 remaining_segments,
256 matching_data,
257 );
258 }
259 self.single_level_wildcard_node
261 .iter()
262 .for_each(|plus_node| {
263 plus_node.collect_matching_subscriptions(
264 remaining_segments,
265 matching_data,
266 )
267 });
268 self.multi_level_wildcard_data
270 .iter()
271 .for_each(|hash_data| matching_data.push(hash_data));
272 }
273 }
274 }
275
276 pub fn find_by_path<'a>(&'a self, topic: &TopicPath) -> Vec<&'a T> {
278 let mut matching_subscribers = Vec::new();
280 self.collect_matching_subscriptions(
281 &topic.segments,
282 &mut matching_subscribers,
283 );
284 matching_subscribers
285 }
286
287 #[cfg(test)]
288 fn collect_active_subscriptions_internal<'a>(
292 &'a self,
293 current_path: &mut Vec<TopicPatternItem>,
294 result: &mut Vec<(TopicPatternPath, &'a T)>,
295 ) {
296 if let Some(data) = &self.exact_match_data {
298 let path =
299 TopicPatternPath::new_from_segments(current_path.as_slice())
300 .expect("Internal path should always be valid");
301 result.push((path, data))
302 };
303 if let Some(data) = &self.multi_level_wildcard_data {
305 current_path.push(TopicPatternItem::Hash(None));
306 let topic_path =
307 TopicPatternPath::new_from_segments(current_path.as_slice())
308 .expect("Internal path should always be valid");
309 result.push((topic_path, data));
310 current_path.pop();
311 };
312 if let Some(plus_node) = &self.single_level_wildcard_node {
313 current_path.push(TopicPatternItem::Plus(None));
314 plus_node
315 .collect_active_subscriptions_internal(current_path, result);
316 current_path.pop();
317 };
318 for (exact_segment, child) in &self.exact_children {
319 current_path.push(TopicPatternItem::Str(exact_segment.clone()));
320 child.collect_active_subscriptions_internal(current_path, result);
321 current_path.pop();
322 }
323 }
324
325 #[cfg(test)]
330 pub fn collect_active_subscriptions(&self) -> Vec<(TopicPatternPath, &T)> {
331 let mut result = Vec::new();
332 self.collect_active_subscriptions_internal(
333 &mut Vec::new(),
334 &mut result,
335 );
336 result
337 }
338}