mqtt_topic_engine/
topic_match.rs1#![allow(clippy::missing_docs_in_private_items)]
8
9use std::fmt;
10use std::ops::Range;
11use std::sync::Arc;
12
13use arcstr::{ArcStr, Substr};
14use smallvec::SmallVec;
15use thiserror::Error;
16
17#[derive(Debug, Clone)]
22pub struct TopicPath {
23 pub path: ArcStr,
25 pub segments: Vec<Substr>,
27}
28
29impl TopicPath {
30 pub fn new(path: impl Into<ArcStr>) -> Self {
32 let path = path.into();
33 let segments: Vec<Substr> =
34 path.split('/').map(|s| path.substr_from(s)).collect();
35 Self { path, segments }
36 }
37
38 pub fn path(&self) -> ArcStr {
40 self.path.clone()
41 }
42}
43
44impl fmt::Display for TopicPath {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 write!(f, "{}", self.path)
47 }
48}
49
50#[derive(Error, Debug, Clone, PartialEq, Eq)]
52pub enum TopicMatchError {
53 #[error("Pattern ended unexpectedly while matching topic")]
55 UnexpectedEndOfPattern,
56
57 #[error("Topic ended unexpectedly while matching pattern")]
59 UnexpectedEndOfTopic,
60
61 #[error("Hash wildcard (#) found in unexpected position")]
63 UnexpectedHashSegment,
64
65 #[error(
67 "Segment mismatch at position {position}: expected '{expected}', \
68 found '{found}'"
69 )]
70 SegmentMismatch {
71 expected: String,
73 found: String,
75 position: usize,
77 },
78
79 #[error("Duplicate parameter name found in pattern")]
81 DuplicateParameterName,
82}
83
84pub struct TopicMatch {
90 topic: Arc<TopicPath>,
91 params: SmallVec<[Range<usize>; 3]>,
92 named_params: SmallVec<[(Substr, Range<usize>); 3]>,
93}
94
95impl TopicMatch {
96 pub(crate) fn from_match_result(
97 topic: Arc<TopicPath>,
98 params: SmallVec<[Range<usize>; 3]>,
99 named_params: SmallVec<[(Substr, Range<usize>); 3]>,
100 ) -> Self {
101 Self {
102 topic,
103 params,
104 named_params,
105 }
106 }
107
108 pub fn path_segments(&self) -> &Vec<Substr> {
110 &self.topic.segments
111 }
112
113 fn get_param_range(&self, range: &Range<usize>) -> Substr {
114 if range.is_empty() {
115 self.topic.path.substr(0 .. 0)
116 } else if range.len() == 1 {
117 self.topic.segments[range.start].clone()
118 } else {
119 let start_segment = &self.topic.segments[range.start];
120 let end_segment = &self.topic.segments[range.end - 1];
121
122 let start_pos = start_segment.as_ptr() as usize
123 - self.topic.path.as_ptr() as usize;
124 let end_pos = end_segment.as_ptr() as usize
125 - self.topic.path.as_ptr() as usize
126 + end_segment.len();
127
128 self.topic.path.substr(start_pos .. end_pos)
129 }
130 }
131
132 pub fn get_param(&self, index: usize) -> Option<Substr> {
137 self.params
138 .get(index)
139 .map(|range| self.get_param_range(range))
140 }
141
142 pub fn get_named_param(&self, name: &str) -> Option<Substr> {
144 self.named_params
145 .iter()
146 .find(|(n, _)| n.as_str() == name)
147 .map(|(_, range)| self.get_param_range(range))
148
149 }
151
152 pub fn topic_path(&self) -> ArcStr {
154 self.topic.path.clone()
155 }
156}
157
158impl fmt::Debug for TopicMatch {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 write!(f, "TopicMatch {{ topic: {}, params: [", self.topic.path)?;
162 for (i, param) in self.params.iter().enumerate() {
163 if i > 0 {
164 write!(f, ", ")?;
165 }
166 write!(f, "{}", self.get_param_range(param))?;
167 }
168 write!(f, "]")?;
169
170 if !self.named_params.is_empty() {
171 write!(f, ", named_params: {{")?;
172 for (name, range) in &self.named_params {
173 write!(f, "{}: {}, ", name, self.get_param_range(range))?;
174 }
175 write!(f, "}}")?;
176 }
177
178 write!(f, " }}")
179 }
180}
181
182impl fmt::Display for TopicMatch {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 write!(f, "Match({})", self.topic.path)?;
185
186 if !self.params.is_empty() {
187 write!(f, " with {} params", self.params.len())?;
188 }
189
190 Ok(())
191 }
192}