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, is_dollar_topic};
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("Invalid topic segment '{segment}' at position {position}")]
23 InvalidSegment {
24 segment: String,
26 position: usize,
28 },
29}
30
31impl TopicMatcherError {
32 pub fn invalid_segment(
34 segment: impl Into<String>,
35 position: usize,
36 ) -> Self {
37 Self::InvalidSegment {
38 segment: segment.into(),
39 position,
40 }
41 }
42}
43
44#[derive(Debug)]
47pub struct TopicMatcherNode<T> {
48 exact_match_data: Option<T>,
50
51 exact_children: HashMap<Substr, TopicMatcherNode<T>>,
53
54 single_level_wildcard_node: Option<Box<TopicMatcherNode<T>>>,
56
57 multi_level_wildcard_data: Option<T>,
59}
60
61pub trait Len {
64 fn len(&self) -> usize;
66 fn is_empty(&self) -> bool {
68 self.len() == 0
69 }
70}
71
72impl<T> Len for HashSet<T> {
73 fn len(&self) -> usize {
74 self.len()
75 }
76 fn is_empty(&self) -> bool {
77 self.is_empty()
78 }
79}
80
81impl<K, V> Len for HashMap<K, V> {
82 fn len(&self) -> usize {
83 self.len()
84 }
85 fn is_empty(&self) -> bool {
86 self.is_empty()
87 }
88}
89
90impl<T: Default + Len> Default for TopicMatcherNode<T> {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96impl<T: Default + Len> TopicMatcherNode<T> {
97 pub fn new() -> Self {
99 Self {
100 exact_match_data: None,
101 exact_children: HashMap::new(),
102 single_level_wildcard_node: None,
103 multi_level_wildcard_data: None,
104 }
105 }
106
107 pub fn is_empty(&self) -> bool {
110 self.exact_match_data.as_ref().is_none_or(T::is_empty)
111 && self.exact_children.is_empty()
112 && self.single_level_wildcard_node.is_none()
113 && self
114 .multi_level_wildcard_data
115 .as_ref()
116 .is_none_or(T::is_empty)
117 }
118 pub fn get_or_create_subscription_table(
120 &mut self,
121 topic_path: &TopicPatternPath,
122 ) -> &mut T {
123 let mut current_node = self;
124
125 let resolved_segments = topic_path.resolve_bound_segments();
126 for segment in resolved_segments {
127 match segment {
128 | TopicPatternItem::Str(s) => {
129 current_node = current_node
130 .exact_children
131 .entry(s.clone())
132 .or_default()
133 }
134 | TopicPatternItem::Plus(_) => {
135 current_node = current_node
136 .single_level_wildcard_node
137 .get_or_insert_with(
138 || Box::new(TopicMatcherNode::new()),
139 )
140 }
141 | TopicPatternItem::Hash(_) => {
142 return current_node
144 .multi_level_wildcard_data
145 .get_or_insert_with(T::default);
146 }
147 }
148 }
149 current_node.exact_match_data.get_or_insert_with(T::default)
150 }
151
152 pub fn update_node<F>(
154 &mut self,
155 topic_path: &[TopicPatternItem],
156 mut f: F,
157 ) -> Result<bool, TopicMatcherError>
158 where
159 F: FnMut(&mut T),
160 {
161 if topic_path.is_empty() {
162 let data = self.exact_match_data.as_mut().ok_or_else(|| {
163 TopicMatcherError::invalid_segment(
164 "no_data_for_empty_path".to_string(),
165 0,
166 )
167 })?;
168 f(data);
169 if data.is_empty() {
170 self.exact_match_data = None
171 }
172 return Ok(self.is_empty());
173 }
174 let current_segment = &topic_path[0];
175 let rest_segments = &topic_path[1 ..];
176
177 match current_segment {
178 | TopicPatternItem::Str(s) => {
179 let child_node =
180 self.exact_children.get_mut(s).ok_or_else(|| {
181 TopicMatcherError::invalid_segment(s.as_str(), 0)
182 })?;
183 if child_node.update_node(rest_segments, f)? {
184 self.exact_children.remove(s);
185 return Ok(self.is_empty());
186 }
187 }
188 | TopicPatternItem::Plus(_) => {
189 let child_node = self
190 .single_level_wildcard_node
191 .as_mut()
192 .ok_or_else(|| {
193 TopicMatcherError::invalid_segment("+".to_string(), 0)
194 })?;
195 if child_node.update_node(rest_segments, f)? {
196 self.single_level_wildcard_node = None;
197 return Ok(self.is_empty());
198 }
199 }
200 | TopicPatternItem::Hash(_) => {
201 let hash_wildcard_data = self
202 .multi_level_wildcard_data
203 .as_mut()
204 .ok_or_else(|| {
205 TopicMatcherError::invalid_segment("#".to_string(), 0)
206 })?;
207 f(hash_wildcard_data);
208 if hash_wildcard_data.is_empty() {
209 self.multi_level_wildcard_data = None;
210 return Ok(self.is_empty());
211 }
212 }
213 }
214 Ok(false)
215 }
216
217 fn collect_matching_subscriptions<'a>(
219 &'a self,
220 topic: &[Substr],
221 at_root: bool,
222 matching_data: &mut Vec<&'a T>,
223 ) {
224 match topic {
225 | [] => {
226 self.exact_match_data
228 .iter()
229 .for_each(|data| matching_data.push(data));
230 self.multi_level_wildcard_data
231 .iter()
232 .for_each(|data| matching_data.push(data))
233 }
234 | [segment, remaining_segments @ ..] => {
235 if let Some(child) = self.exact_children.get(segment) {
237 child.collect_matching_subscriptions(
238 remaining_segments,
239 false,
240 matching_data,
241 );
242 }
243 if at_root && is_dollar_topic(segment) {
248 return;
249 }
250 self.single_level_wildcard_node
252 .iter()
253 .for_each(|plus_node| {
254 plus_node.collect_matching_subscriptions(
255 remaining_segments,
256 false,
257 matching_data,
258 )
259 });
260 self.multi_level_wildcard_data
262 .iter()
263 .for_each(|hash_data| matching_data.push(hash_data));
264 }
265 }
266 }
267
268 pub fn find_by_path<'a>(&'a self, topic: &TopicPath) -> Vec<&'a T> {
270 let mut matching_subscribers = Vec::new();
272 self.collect_matching_subscriptions(
273 &topic.segments,
274 true,
275 &mut matching_subscribers,
276 );
277 matching_subscribers
278 }
279
280 #[cfg(test)]
281 fn collect_active_subscriptions_internal<'a>(
285 &'a self,
286 current_path: &mut Vec<TopicPatternItem>,
287 result: &mut Vec<(TopicPatternPath, &'a T)>,
288 ) {
289 if let Some(data) = &self.exact_match_data {
291 let path =
292 TopicPatternPath::new_from_segments(current_path.as_slice())
293 .expect("Internal path should always be valid");
294 result.push((path, data))
295 };
296 if let Some(data) = &self.multi_level_wildcard_data {
298 current_path.push(TopicPatternItem::Hash(None));
299 let topic_path =
300 TopicPatternPath::new_from_segments(current_path.as_slice())
301 .expect("Internal path should always be valid");
302 result.push((topic_path, data));
303 current_path.pop();
304 };
305 if let Some(plus_node) = &self.single_level_wildcard_node {
306 current_path.push(TopicPatternItem::Plus(None));
307 plus_node
308 .collect_active_subscriptions_internal(current_path, result);
309 current_path.pop();
310 };
311 for (exact_segment, child) in &self.exact_children {
312 current_path.push(TopicPatternItem::Str(exact_segment.clone()));
313 child.collect_active_subscriptions_internal(current_path, result);
314 current_path.pop();
315 }
316 }
317
318 #[cfg(test)]
323 pub fn collect_active_subscriptions(&self) -> Vec<(TopicPatternPath, &T)> {
324 let mut result = Vec::new();
325 self.collect_active_subscriptions_internal(
326 &mut Vec::new(),
327 &mut result,
328 );
329 result
330 }
331}