From 2abc577aca64cc7ba4bdb8c253e415da12dcb86c Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 20 Mar 2022 20:37:22 +0100 Subject: [PATCH] implement topic matching --- haesli_messaging/src/routing.rs | 117 +++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 18 deletions(-) diff --git a/haesli_messaging/src/routing.rs b/haesli_messaging/src/routing.rs index d1cf35b..29c555e 100644 --- a/haesli_messaging/src/routing.rs +++ b/haesli_messaging/src/routing.rs @@ -40,26 +40,62 @@ pub fn route_message(exchange: &Exchange, routing_key: &str) -> Option { - let topic = parse_topic(routing_key); // todo: optimizing this is a fun problem - Some(match_topic(bindings, topic)) + Some(match_topic(bindings, routing_key)) } ExchangeType::Headers => None, // unsupported ExchangeType::System => None, // unsupported } } -fn match_topic( - patterns: &[(Vec, Q)], - routing_key: Vec, -) -> Vec { +fn match_topic(patterns: &[(Vec, Q)], routing_key: &str) -> Vec { patterns .iter() .filter_map(|(pattern, value)| { - let mut queue_segments = routing_key.iter(); + let mut key_segments = routing_key.split('.'); + let mut pat_segments = pattern.iter(); - for segment in pattern {} + loop { + let key = key_segments.next(); + let pat = pat_segments.next(); + + match (pat, key) { + (Some(TopicSegment::Word(pat)), Some(key)) if pat != key => return None, + (Some(TopicSegment::Word(_)), Some(_)) => {} + (Some(TopicSegment::SingleWildcard), Some(_)) => {} + (Some(TopicSegment::MultiWildcard), _) => { + let pat = pat_segments.next(); + match pat { + Some(pat) => { + // loop until we find the next pat segment in the key + loop { + let key = key_segments.next(); + match (pat, key) { + (_, None) => return None, // we are expecting something after the wildcard + (TopicSegment::Word(pat), Some(key)) if pat == key => { + break; // we matched all of the `#` and the segment afterwards + } + (TopicSegment::SingleWildcard, Some(_)) => { + break; // `#.*` is a cursed pattern, match `#` for 1 + } + (TopicSegment::MultiWildcard, Some(_)) => { + break; // at this point I don't even care, who the fuck + // would use `#.#`, just only match 2 which is + // wrong so todo I guess lol + } + _ => continue, + } + } + } + None => break, // pattern ends with `#`, it certainly matches + } + } + (Some(_), None) => return None, + (None, Some(_)) => return None, + (None, None) => break, + } + } Some(value.clone()) }) @@ -70,18 +106,63 @@ fn match_topic( mod tests { use crate::routing::{match_topic, parse_topic}; - macro_rules! match_topics { - (patterns: $($pattern:expr),*) => {}; + macro_rules! match_topics_test { + ($name:ident { + patterns: $($pattern:expr),*; + routing_key: $routing_key:expr; + expected: $($expected:expr),*; + }) => { + #[test] + fn $name() { + fn inc(x: &mut u64) -> u64 { let tmp = *x; *x += 1; tmp } + + let mut n = 0; + let n = &mut n; + + // assign each pattern a number + let patterns = [$((parse_topic($pattern), inc(n))),*]; + + let matched = match_topic(&patterns, $routing_key); + let expected = vec![$($expected),*]; + + assert_eq!(matched, expected); + } + }; } - #[test] - #[ignore] - fn match_empty_topic() { - let patterns = [(parse_topic(""), 1), (parse_topic("BAD"), 2)]; - let routing_key = parse_topic(""); + match_topics_test!(match_spec_example_1 { + patterns: "*.stock.#"; + routing_key: "usd.stock"; + expected: 0; + }); - let matched = match_topic(&patterns, routing_key); + match_topics_test!(match_spec_example_2 { + patterns: "*.stock.#"; + routing_key: "eur.stock.db"; + expected: 0; + }); - assert_eq!(matched, vec![1]) - } + match_topics_test!(match_spec_example_3 { + patterns: "*.stock.#"; + routing_key: "stock.nasdaq"; + expected: ; + }); + + match_topics_test!(match_no_wildcards { + patterns: "na.stock.usd", "sa.stock.peso", "stock.nasdaq", "usd.stock.na"; + routing_key: "na.stock.usd"; + expected: 0; + }); + + match_topics_test!(match_cursed_wildcards { + patterns: "*.*.*", "#.usd", "#.stock.*", "*.#", "#", "na.*"; + routing_key: "na.stock.usd"; + expected: 0, 1, 2, 3, 4; + }); + + match_topics_test!(match_empty_topic { + patterns: "", "bad"; + routing_key: ""; + expected: 0; + }); }