mirror of
https://github.com/Noratrieb/haesli.git
synced 2026-01-14 11:45:02 +01:00
implement topic matching
This commit is contained in:
parent
43d0ce05dc
commit
2abc577aca
1 changed files with 99 additions and 18 deletions
|
|
@ -40,26 +40,62 @@ pub fn route_message(exchange: &Exchange, routing_key: &str) -> Option<Vec<Queue
|
||||||
Some(bindings.clone()) // see, this is actually Not That Bad I Hope
|
Some(bindings.clone()) // see, this is actually Not That Bad I Hope
|
||||||
}
|
}
|
||||||
ExchangeType::Topic { bindings } => {
|
ExchangeType::Topic { bindings } => {
|
||||||
let topic = parse_topic(routing_key);
|
|
||||||
// todo: optimizing this is a fun problem
|
// todo: optimizing this is a fun problem
|
||||||
|
|
||||||
Some(match_topic(bindings, topic))
|
Some(match_topic(bindings, routing_key))
|
||||||
}
|
}
|
||||||
ExchangeType::Headers => None, // unsupported
|
ExchangeType::Headers => None, // unsupported
|
||||||
ExchangeType::System => None, // unsupported
|
ExchangeType::System => None, // unsupported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_topic<Q: Clone>(
|
fn match_topic<Q: Clone>(patterns: &[(Vec<TopicSegment>, Q)], routing_key: &str) -> Vec<Q> {
|
||||||
patterns: &[(Vec<TopicSegment>, Q)],
|
|
||||||
routing_key: Vec<TopicSegment>,
|
|
||||||
) -> Vec<Q> {
|
|
||||||
patterns
|
patterns
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(pattern, value)| {
|
.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())
|
Some(value.clone())
|
||||||
})
|
})
|
||||||
|
|
@ -70,18 +106,63 @@ fn match_topic<Q: Clone>(
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::routing::{match_topic, parse_topic};
|
use crate::routing::{match_topic, parse_topic};
|
||||||
|
|
||||||
macro_rules! match_topics {
|
macro_rules! match_topics_test {
|
||||||
(patterns: $($pattern:expr),*) => {};
|
($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]
|
match_topics_test!(match_spec_example_1 {
|
||||||
#[ignore]
|
patterns: "*.stock.#";
|
||||||
fn match_empty_topic() {
|
routing_key: "usd.stock";
|
||||||
let patterns = [(parse_topic(""), 1), (parse_topic("BAD"), 2)];
|
expected: 0;
|
||||||
let routing_key = parse_topic("");
|
});
|
||||||
|
|
||||||
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;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue