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