rumqttc/mqttbytes/
topic.rs1pub fn has_wildcards(s: &str) -> bool {
3 s.contains('+') || s.contains('#')
4}
5
6pub fn valid_topic(topic: &str) -> bool {
8 if topic.contains('+') || topic.contains('#') {
10 return false;
11 }
12
13 true
14}
15
16pub fn valid_filter(filter: &str) -> bool {
20 if filter.is_empty() {
21 return false;
22 }
23
24 let mut hirerarchy = filter.split('/').rev();
26
27 let last = hirerarchy.next().unwrap();
31
32 if last.len() != 1 && (last.contains('#') || last.contains('+')) {
36 return false;
37 }
38
39 for entry in hirerarchy {
41 if entry.contains('#') {
45 return false;
46 }
47
48 if entry.len() > 1 && entry.contains('+') {
51 return false;
52 }
53 }
54
55 true
56}
57
58pub fn matches(topic: &str, filter: &str) -> bool {
64 if !topic.is_empty() && topic[..1].contains('$') {
65 return false;
66 }
67
68 let mut topics = topic.split('/');
69 let mut filters = filter.split('/');
70
71 for f in filters.by_ref() {
72 if f == "#" {
74 return true;
75 }
76
77 let top = topics.next();
81 match top {
82 Some("#") => return false,
83 Some(_) if f == "+" => continue,
84 Some(t) if f != t => return false,
85 Some(_) => continue,
86 None => return false,
87 }
88 }
89
90 if topics.next().is_some() {
92 return false;
93 }
94
95 true
96}
97
98#[cfg(test)]
99mod test {
100 #[test]
101 fn wildcards_are_detected_correctly() {
102 assert!(!super::has_wildcards("a/b/c"));
103 assert!(super::has_wildcards("a/+/c"));
104 assert!(super::has_wildcards("a/b/#"));
105 }
106
107 #[test]
108 fn topics_are_validated_correctly() {
109 assert!(!super::valid_topic("+wrong"));
110 assert!(!super::valid_topic("wro#ng"));
111 assert!(!super::valid_topic("w/r/o/n/g+"));
112 assert!(!super::valid_topic("wrong/#/path"));
113 }
114
115 #[test]
116 fn filters_are_validated_correctly() {
117 assert!(!super::valid_filter("wrong/#/filter"));
118 assert!(!super::valid_filter("wrong/wr#ng/filter"));
119 assert!(!super::valid_filter("wrong/filter#"));
120 assert!(super::valid_filter("correct/filter/#"));
121 assert!(!super::valid_filter("wr/o+/ng"));
122 assert!(!super::valid_filter("wr/+o+/ng"));
123 assert!(!super::valid_filter("wron/+g"));
124 assert!(super::valid_filter("cor/+/rect/+"));
125 }
126
127 #[test]
128 fn zero_len_subscriptions_are_not_allowed() {
129 assert!(!super::valid_filter(""));
130 }
131
132 #[test]
133 fn dollar_subscriptions_doesnt_match_dollar_topic() {
134 assert!(super::matches("sy$tem/metrics", "sy$tem/+"));
135 assert!(!super::matches("$system/metrics", "$system/+"));
136 assert!(!super::matches("$system/metrics", "+/+"));
137 }
138
139 #[test]
140 fn topics_match_with_filters_as_expected() {
141 let topic = "a/b/c";
142 let filter = "a/b/c";
143 assert!(super::matches(topic, filter));
144
145 let topic = "a/b/c";
146 let filter = "d/b/c";
147 assert!(!super::matches(topic, filter));
148
149 let topic = "a/b/c";
150 let filter = "a/b/e";
151 assert!(!super::matches(topic, filter));
152
153 let topic = "a/b/c";
154 let filter = "a/b/c/d";
155 assert!(!super::matches(topic, filter));
156
157 let topic = "a/b/c";
158 let filter = "#";
159 assert!(super::matches(topic, filter));
160
161 let topic = "a/b/c";
162 let filter = "a/b/c/#";
163 assert!(super::matches(topic, filter));
164
165 let topic = "a/b/c/d";
166 let filter = "a/b/c";
167 assert!(!super::matches(topic, filter));
168
169 let topic = "a/b/c/d";
170 let filter = "a/b/c/#";
171 assert!(super::matches(topic, filter));
172
173 let topic = "a/b/c/d/e/f";
174 let filter = "a/b/c/#";
175 assert!(super::matches(topic, filter));
176
177 let topic = "a/b/c";
178 let filter = "a/+/c";
179 assert!(super::matches(topic, filter));
180 let topic = "a/b/c/d/e";
181 let filter = "a/+/c/+/e";
182 assert!(super::matches(topic, filter));
183
184 let topic = "a/b";
185 let filter = "a/b/+";
186 assert!(!super::matches(topic, filter));
187
188 let filter1 = "a/b/+";
189 let filter2 = "a/b/#";
190 assert!(super::matches(filter1, filter2));
191 assert!(!super::matches(filter2, filter1));
192
193 let filter1 = "a/b/+";
194 let filter2 = "#";
195 assert!(super::matches(filter1, filter2));
196
197 let filter1 = "a/+/c/d";
198 let filter2 = "a/+/+/d";
199 assert!(super::matches(filter1, filter2));
200 assert!(!super::matches(filter2, filter1));
201
202 let filter1 = "a/b/c/d/e";
203 let filter2 = "a/+/+/+/e";
204 assert!(super::matches(filter1, filter2));
205
206 let filter1 = "a/+/c/+/e";
207 let filter2 = "a/+/+/+/e";
208 assert!(super::matches(filter1, filter2));
209
210 let filter1 = "a/+/+/+/e";
211 let filter2 = "a/+/+/+/e";
212 assert!(super::matches(filter1, filter2));
213 }
214}