1use log::{debug, error, warn};
2use serde::{Deserialize, Serialize};
3
4use super::three_part_topic::TetherOrCustomTopic;
5
6pub trait PlugDefinitionCommon<'a> {
7 fn name(&'a self) -> &'a str;
8 fn topic_str(&'a self) -> &'a str;
9 fn topic(&'a self) -> &'a TetherOrCustomTopic;
10 fn qos(&'a self) -> i32;
11}
12
13#[derive(Serialize, Deserialize, Debug)]
14pub struct InputPlugDefinition {
15 name: String,
16 topic: TetherOrCustomTopic,
17 qos: i32,
18}
19
20impl PlugDefinitionCommon<'_> for InputPlugDefinition {
21 fn name(&self) -> &str {
22 &self.name
23 }
24
25 fn topic_str(&self) -> &str {
26 match &self.topic {
27 TetherOrCustomTopic::Custom(s) => {
28 debug!("Plug named \"{}\" has custom topic \"{}\"", &self.name, &s);
29 s
30 }
31 TetherOrCustomTopic::Tether(t) => {
32 debug!(
33 "Plug named \"{}\" has Three Part topic \"{:?}\"",
34 &self.name, t
35 );
36 t.topic()
37 }
38 }
39 }
40
41 fn topic(&'_ self) -> &'_ TetherOrCustomTopic {
42 &self.topic
43 }
44
45 fn qos(&self) -> i32 {
46 self.qos
47 }
48}
49
50impl InputPlugDefinition {
51 pub fn new(name: &str, topic: TetherOrCustomTopic, qos: Option<i32>) -> InputPlugDefinition {
52 InputPlugDefinition {
53 name: String::from(name),
54 topic,
55 qos: qos.unwrap_or(1),
56 }
57 }
58
59 pub fn matches(&self, incoming_topic: &TetherOrCustomTopic) -> bool {
70 match incoming_topic {
71 TetherOrCustomTopic::Tether(incoming_three_parts) => match &self.topic {
72 TetherOrCustomTopic::Tether(my_tpt) => {
73 let matches_role =
74 my_tpt.role() == "+" || my_tpt.role().eq(incoming_three_parts.role());
75 let matches_id =
76 my_tpt.id() == "+" || my_tpt.id().eq(incoming_three_parts.id());
77 let matches_plug_name = my_tpt.plug_name() == "+"
78 || my_tpt.plug_name().eq(incoming_three_parts.plug_name());
79 debug!("Test match for plug named \"{}\" with def {:?} against {:?} => matches_role? {}, matches_id? {}, matches_plug_name? {}", &self.name, &self.topic, &incoming_three_parts, matches_role, matches_id, matches_plug_name);
80 matches_role && matches_id && matches_plug_name
81 }
82 TetherOrCustomTopic::Custom(my_custom_topic) => {
83 debug!(
84 "Custom/manual topic \"{}\" on Plug \"{}\" cannot be matched automatically; please filter manually for this",
85 &my_custom_topic,
86 self.name()
87 );
88 my_custom_topic.as_str() == "#"
89 || my_custom_topic.as_str() == incoming_three_parts.topic()
90 }
91 },
92 TetherOrCustomTopic::Custom(incoming_custom) => match &self.topic {
93 TetherOrCustomTopic::Custom(my_custom_topic) => {
94 if my_custom_topic.as_str() == "#"
95 || my_custom_topic.as_str() == incoming_custom.as_str()
96 {
97 true
98 } else {
99 warn!(
100 "Incoming topic \"{}\" is not a three-part topic",
101 &incoming_custom
102 );
103 false
104 }
105 }
106 TetherOrCustomTopic::Tether(_) => {
107 error!("Incoming is NOT Three Part Topic but this plug DOES have Three Part Topic; cannot decide match");
108 false
109 }
110 },
111 }
112 }
113}
114
115#[derive(Serialize, Deserialize, Debug)]
116pub struct OutputPlugDefinition {
117 name: String,
118 topic: TetherOrCustomTopic,
119 qos: i32,
120 retain: bool,
121}
122
123impl PlugDefinitionCommon<'_> for OutputPlugDefinition {
124 fn name(&'_ self) -> &'_ str {
125 &self.name
126 }
127
128 fn topic_str(&self) -> &str {
129 match &self.topic {
130 TetherOrCustomTopic::Custom(s) => s,
131 TetherOrCustomTopic::Tether(t) => t.topic(),
132 }
133 }
134
135 fn topic(&'_ self) -> &'_ TetherOrCustomTopic {
136 &self.topic
137 }
138
139 fn qos(&'_ self) -> i32 {
140 self.qos
141 }
142}
143
144impl OutputPlugDefinition {
145 pub fn new(
146 name: &str,
147 topic: TetherOrCustomTopic,
148 qos: Option<i32>,
149 retain: Option<bool>,
150 ) -> OutputPlugDefinition {
151 OutputPlugDefinition {
152 name: String::from(name),
153 topic,
154 qos: qos.unwrap_or(1),
155 retain: retain.unwrap_or(false),
156 }
157 }
158
159 pub fn retain(&self) -> bool {
160 self.retain
161 }
162}
163
164#[derive(Serialize, Deserialize, Debug)]
165pub enum PlugDefinition {
166 InputPlug(InputPlugDefinition),
167 OutputPlug(OutputPlugDefinition),
168}
169
170impl PlugDefinition {
171 pub fn name(&self) -> &str {
172 match self {
173 PlugDefinition::InputPlug(p) => p.name(),
174 PlugDefinition::OutputPlug(p) => p.name(),
175 }
176 }
177
178 pub fn topic(&self) -> &str {
179 match self {
180 PlugDefinition::InputPlug(p) => p.topic_str(),
181 PlugDefinition::OutputPlug(p) => p.topic_str(),
182 }
183 }
184
185 pub fn matches(&self, topic: &TetherOrCustomTopic) -> bool {
186 match self {
187 PlugDefinition::InputPlug(p) => p.matches(topic),
188 PlugDefinition::OutputPlug(_) => {
189 error!("We don't check matches for Output Plugs");
190 false
191 }
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198
199 use crate::{
200 three_part_topic::{parse_plug_name, TetherOrCustomTopic, ThreePartTopic},
201 InputPlugDefinition, PlugDefinitionCommon,
202 };
203
204 #[test]
205 fn input_match_tpt() {
206 let plug_def = InputPlugDefinition::new(
207 "testPlug",
208 TetherOrCustomTopic::Tether(ThreePartTopic::new_for_subscribe(
209 "testPlug", None, None, None,
210 )),
211 None,
212 );
213
214 assert_eq!(&plug_def.name, "testPlug");
215 assert_eq!(plug_def.topic_str(), "+/+/testPlug");
216 assert_eq!(parse_plug_name("+/+/testPlug"), Some("testPlug"));
217 assert!(
218 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
219 "dummy", "any", "testPlug"
220 )))
221 );
222 assert!(
223 !plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
224 "dummy",
225 "any",
226 "anotherPlug"
227 )))
228 );
229 }
231
232 #[test]
233 fn input_match_tpt_custom_role() {
234 let plug_def = InputPlugDefinition::new(
235 "customPlug",
236 TetherOrCustomTopic::Tether(ThreePartTopic::new_for_subscribe(
237 "customPlug",
238 Some("customRole"),
239 None,
240 None,
241 )),
242 None,
243 );
244
245 assert_eq!(&plug_def.name, "customPlug");
246 assert_eq!(plug_def.topic_str(), "customRole/+/customPlug");
247 assert!(
248 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
249 "customRole",
250 "any",
251 "customPlug"
252 )))
253 );
254 assert!(
255 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
256 "customRole",
257 "andAnythingElse",
258 "customPlug"
259 )))
260 );
261 assert!(
262 !plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
263 "customRole",
264 "any",
265 "notMyPlug"
266 )))
267 ); assert!(
269 !plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
270 "someOtherRole",
271 "any",
272 "customPlug"
273 )))
274 ); }
276
277 #[test]
278 fn input_match_custom_id() {
279 let plug_def = InputPlugDefinition::new(
280 "customPlug",
281 TetherOrCustomTopic::Tether(ThreePartTopic::new_for_subscribe(
282 "customPlug",
283 None,
284 Some("specificID"),
285 None,
286 )),
287 None,
288 );
289
290 assert_eq!(&plug_def.name, "customPlug");
291 assert_eq!(plug_def.topic_str(), "+/specificID/customPlug");
292 assert!(
293 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
294 "anyRole",
295 "specificID",
296 "customPlug"
297 )))
298 );
299 assert!(
300 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
301 "anotherRole",
302 "specificID",
303 "customPlug"
304 )))
305 ); assert!(
307 !plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
308 "anyRole",
309 "specificID",
310 "notMyPlug"
311 )))
312 ); assert!(
314 !plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
315 "anyRole",
316 "anotherID",
317 "customPlug"
318 )))
319 ); }
321
322 #[test]
323 fn input_match_both() {
324 let plug_def = InputPlugDefinition::new(
325 "customPlug",
326 TetherOrCustomTopic::Tether(ThreePartTopic::new_for_subscribe(
327 "customPlug",
328 Some("specificRole"),
329 Some("specificID"),
330 None,
331 )),
332 None,
333 );
334
335 assert_eq!(&plug_def.name, "customPlug");
336 assert_eq!(plug_def.topic_str(), "specificRole/specificID/customPlug");
337 assert!(
338 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
339 "specificRole",
340 "specificID",
341 "customPlug"
342 )))
343 );
344 assert!(!plug_def.matches(&TetherOrCustomTopic::Custom(
345 "specificRole/specificID/notMyPlug".into()
346 ))); assert!(!plug_def.matches(&TetherOrCustomTopic::Custom(
348 "specificRole/anotherID/customPlug".into()
349 ))); assert!(!plug_def.matches(&TetherOrCustomTopic::Custom(
351 "anotherRole/anotherID/customPlug".into()
352 ))); }
354
355 #[test]
356 fn input_match_custom_topic() {
357 let plug_def = InputPlugDefinition::new(
358 "customPlug",
359 TetherOrCustomTopic::Custom("one/two/three/four/five".into()), None,
361 );
362
363 assert_eq!(plug_def.name(), "customPlug");
364 assert!(plug_def.matches(&TetherOrCustomTopic::Custom(
366 "one/two/three/four/five".into()
367 )));
368
369 assert!(!plug_def.matches(&TetherOrCustomTopic::Custom("one/one/one/one/one".into())));
371 }
372
373 #[test]
374 fn input_match_wildcard() {
375 let plug_def = InputPlugDefinition::new(
376 "everything",
377 TetherOrCustomTopic::Custom("#".into()), None,
379 );
380
381 assert_eq!(plug_def.name(), "everything");
382
383 assert!(
385 plug_def.matches(&TetherOrCustomTopic::Tether(ThreePartTopic::new(
386 "any", "any", "plugName"
387 )))
388 );
389
390 assert!(plug_def.matches(&TetherOrCustomTopic::Custom(
392 "one/two/three/four/five".into()
393 )));
394 }
395}