1pub fn has_wildcards(s: &str) -> bool {
3 s.contains("+") || s.contains("#")
4}
5
6pub fn valid_topic(topic: &str) -> bool {
8 if topic.contains("+") {
9 return false;
10 }
11
12 if topic.contains("#") {
13 return false;
14 }
15
16 true
17}
18
19pub fn valid_filter(filter: &str) -> bool {
22 if filter.len() == 0 {
23 return false;
24 }
25
26 let hirerarchy = filter.split("/").collect::<Vec<&str>>();
27 if let Some((last, remaining)) = hirerarchy.split_last() {
28 for entry in remaining.iter() {
32 if entry.contains("#") {
33 return false;
34 }
35 }
36
37 if last.len() != 1 && last.contains("#") {
40 return false;
41 }
42 }
43
44 true
45}
46
47pub fn matches(topic: &str, filter: &str) -> bool {
51 if topic.len() > 0 && topic[..1].contains("$") {
52 return false;
53 }
54
55 let mut topics = topic.split("/");
56 let mut filters = filter.split("/");
57
58 for f in filters.by_ref() {
59 if f == "#" {
61 return true;
62 }
63
64 let top = topics.next();
68 match top {
69 Some(t) if t == "#" => return false,
70 Some(_) if f == "+" => continue,
71 Some(t) if f != t => return false,
72 Some(_) => continue,
73 None => return false,
74 }
75 }
76
77 if topics.next().is_some() {
79 return false;
80 }
81
82 true
83}
84
85#[cfg(test)]
86mod test {
87 #[test]
88 fn wildcards_are_detected_correctly() {
89 assert!(!super::has_wildcards("a/b/c"));
90 assert!(super::has_wildcards("a/+/c"));
91 assert!(super::has_wildcards("a/b/#"));
92 }
93
94 #[test]
95 fn topics_are_validated_correctly() {
96 assert!(!super::valid_topic("+wrong"));
97 assert!(!super::valid_topic("wro#ng"));
98 assert!(!super::valid_topic("w/r/o/n/g+"));
99 assert!(!super::valid_topic("wrong/#/path"));
100 }
101
102 #[test]
103 fn filters_are_validated_correctly() {
104 assert!(!super::valid_filter("wrong/#/filter"));
105 assert!(!super::valid_filter("wrong/wr#ng/filter"));
106 assert!(!super::valid_filter("wrong/filter#"));
107 assert!(super::valid_filter("correct/filter/#"));
108 }
109
110 #[test]
111 fn zero_len_subscriptions_are_not_allowed() {
112 assert!(!super::valid_filter(""));
113 }
114
115 #[test]
116 fn dollar_subscriptions_doesnt_match_dollar_topic() {
117 assert!(super::matches("sy$tem/metrics", "sy$tem/+"));
118 assert!(!super::matches("$system/metrics", "$system/+"));
119 assert!(!super::matches("$system/metrics", "+/+"));
120 }
121
122 #[test]
123 fn topics_match_with_filters_as_expected() {
124 let topic = "a/b/c";
125 let filter = "a/b/c";
126 assert!(super::matches(topic, filter));
127
128 let topic = "a/b/c";
129 let filter = "d/b/c";
130 assert!(!super::matches(topic, filter));
131
132 let topic = "a/b/c";
133 let filter = "a/b/e";
134 assert!(!super::matches(topic, filter));
135
136 let topic = "a/b/c";
137 let filter = "a/b/c/d";
138 assert!(!super::matches(topic, filter));
139
140 let topic = "a/b/c";
141 let filter = "#";
142 assert!(super::matches(topic, filter));
143
144 let topic = "a/b/c";
145 let filter = "a/b/c/#";
146 assert!(super::matches(topic, filter));
147
148 let topic = "a/b/c/d";
149 let filter = "a/b/c";
150 assert!(!super::matches(topic, filter));
151
152 let topic = "a/b/c/d";
153 let filter = "a/b/c/#";
154 assert!(super::matches(topic, filter));
155
156 let topic = "a/b/c/d/e/f";
157 let filter = "a/b/c/#";
158 assert!(super::matches(topic, filter));
159
160 let topic = "a/b/c";
161 let filter = "a/+/c";
162 assert!(super::matches(topic, filter));
163 let topic = "a/b/c/d/e";
164 let filter = "a/+/c/+/e";
165 assert!(super::matches(topic, filter));
166
167 let topic = "a/b";
168 let filter = "a/b/+";
169 assert!(!super::matches(topic, filter));
170
171 let filter1 = "a/b/+";
172 let filter2 = "a/b/#";
173 assert!(super::matches(filter1, filter2));
174 assert!(!super::matches(filter2, filter1));
175
176 let filter1 = "a/b/+";
177 let filter2 = "#";
178 assert!(super::matches(filter1, filter2));
179
180 let filter1 = "a/+/c/d";
181 let filter2 = "a/+/+/d";
182 assert!(super::matches(filter1, filter2));
183 assert!(!super::matches(filter2, filter1));
184 }
185}