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
84#[derive(Clone)]
93pub struct TopicMatch {
94 topic: Arc<TopicPath>,
95 params: SmallVec<[Range<usize>; 3]>,
96 named_params: SmallVec<[(Substr, Range<usize>); 3]>,
97}
98
99impl TopicMatch {
100 pub(crate) fn from_match_result(
101 topic: Arc<TopicPath>,
102 params: SmallVec<[Range<usize>; 3]>,
103 named_params: SmallVec<[(Substr, Range<usize>); 3]>,
104 ) -> Self {
105 Self {
106 topic,
107 params,
108 named_params,
109 }
110 }
111
112 pub fn path_segments(&self) -> &Vec<Substr> {
114 &self.topic.segments
115 }
116
117 fn get_param_range(&self, range: &Range<usize>) -> Substr {
118 if range.is_empty() {
119 self.topic.path.substr(0 .. 0)
120 } else if range.len() == 1 {
121 self.topic.segments[range.start].clone()
122 } else {
123 let start_segment = &self.topic.segments[range.start];
124 let end_segment = &self.topic.segments[range.end - 1];
125
126 let start_pos = start_segment.as_ptr() as usize
127 - self.topic.path.as_ptr() as usize;
128 let end_pos = end_segment.as_ptr() as usize
129 - self.topic.path.as_ptr() as usize
130 + end_segment.len();
131
132 self.topic.path.substr(start_pos .. end_pos)
133 }
134 }
135
136 pub fn get_param(&self, index: usize) -> Option<Substr> {
141 self.params
142 .get(index)
143 .map(|range| self.get_param_range(range))
144 }
145
146 pub fn get_named_param(&self, name: &str) -> Option<Substr> {
148 self.named_params
149 .iter()
150 .find(|(n, _)| n.as_str() == name)
151 .map(|(_, range)| self.get_param_range(range))
152
153 }
155
156 pub fn topic_path(&self) -> ArcStr {
158 self.topic.path.clone()
159 }
160}
161
162impl fmt::Debug for TopicMatch {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(f, "TopicMatch {{ topic: {}, params: [", self.topic.path)?;
166 for (i, param) in self.params.iter().enumerate() {
167 if i > 0 {
168 write!(f, ", ")?;
169 }
170 write!(f, "{}", self.get_param_range(param))?;
171 }
172 write!(f, "]")?;
173
174 if !self.named_params.is_empty() {
175 write!(f, ", named_params: {{")?;
176 for (name, range) in &self.named_params {
177 write!(f, "{}: {}, ", name, self.get_param_range(range))?;
178 }
179 write!(f, "}}")?;
180 }
181
182 write!(f, " }}")
183 }
184}
185
186impl fmt::Display for TopicMatch {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 write!(f, "Match({})", self.topic.path)?;
189
190 if !self.params.is_empty() {
191 write!(f, " with {} params", self.params.len())?;
192 }
193
194 Ok(())
195 }
196}