Skip to content

Commit

Permalink
Fixed TopicMatcher and TopicFilter. Added topic_matcher() functions f…
Browse files Browse the repository at this point in the history
…rom PR #228
  • Loading branch information
fpagliughi committed May 20, 2024
1 parent cf953b9 commit a08a941
Show file tree
Hide file tree
Showing 4 changed files with 424 additions and 215 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v0.12.4](https://github.com/eclipse/paho.mqtt.rust/compare/v0.12.3..v0.12.4) - 2024-05-19

- Fixes for topic matching:
- `TopicMatcher`
- Fixed a number of corner cases
- Iterator optimized
- Added `prune()` and `shrink_to_fit()`, and `get_key_value()`
- `TopicFilter` fixed corner cases
- Added stand-alone `topic_matches()` and `topic_matches_iter()` functions from [PR #228](https://github.com/eclipse/paho.mqtt.rust/pull/228)


## [v0.12.3](https://github.com/eclipse/paho.mqtt.rust/compare/v0.12.2..v0.12.3) - 2023-10-25

- The -sys crate now wraps Paho C v1.3.13, fixing several issues, including crashes on reconnect callbacks.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "paho-mqtt"
version = "0.12.3"
version = "0.12.4"
edition = "2021"
rust-version = "1.63"
authors = ["Frank Pagliughi <[email protected]>"]
Expand Down
78 changes: 56 additions & 22 deletions src/topic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,40 +457,45 @@ impl TopicFilter {
Ok(v)
}

/// Creates a new topic filter from the string without checking it.
pub fn new_unchecked<S>(filter: S) -> Self
where
S: Into<String>,
{
let filter = filter.into();

if filter.contains('+') || filter.ends_with('#') {
Self::Fields(filter.split('/').map(|s| s.to_string()).collect())
}
else {
Self::Topic(filter)
}
}

/// Determines if the topic matches the filter.
pub fn is_match(&self, topic: &str) -> bool {
///
/// This is the same as [`is_match`](Self::is_match), but uses a more
/// consistent function name with other topic matchers.
pub fn matches(&self, topic: &str) -> bool {
use crate::topic_matcher::topic_matches_iter;
match self {
Self::Topic(filter) => topic == filter,
Self::Fields(fields) => {
let n = fields.len();
let top_fields: Vec<_> = topic.split('/').collect();

if n > top_fields.len() {
false
}
else {
let mut saw_wc = false;
for i in 0..n {
if fields[i] == "#" {
saw_wc = true;
break;
}
if fields[i] != "+" && fields[i] != top_fields[i] {
return false;
}
}
saw_wc || n == top_fields.len()
}
topic_matches_iter(fields.iter().map(|s| s.as_str()), topic.split('/'))
}
}
}

/// Determines if the topic matches the filter.
pub fn is_match(&self, topic: &str) -> bool {
self.matches(topic)
}
}

impl fmt::Display for TopicFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Topic(filter) => write!(f, "{}", filter),
// OPTIIMIZE: Do the individual writes, not join
Self::Fields(fields) => write!(f, "{}", fields.join("/")),
}
}
Expand All @@ -516,7 +521,7 @@ mod tests {
}

#[test]
fn test_topic_filter() {
fn test_basic_topic_filter() {
const FILTER1: &str = "some/topic/#";

let filter = TopicFilter::new(FILTER1).unwrap();
Expand All @@ -538,4 +543,33 @@ mod tests {
assert!(filter.is_match("some/thing"));
assert!(!filter.is_match("some/thing/plus"));
}

#[test]
fn test_topic_filter() {
// Should match

assert!(TopicFilter::new_unchecked("foo/bar").matches("foo/bar"));
assert!(TopicFilter::new_unchecked("foo/+").matches("foo/bar"));
assert!(TopicFilter::new_unchecked("foo/+/baz").matches("foo/bar/baz"));
assert!(TopicFilter::new_unchecked("foo/+/#").matches("foo/bar/baz"));
assert!(TopicFilter::new_unchecked("A/B/+/#").matches("A/B/B/C"));
assert!(TopicFilter::new_unchecked("#").matches("foo/bar/baz"));
assert!(TopicFilter::new_unchecked("#").matches("/foo/bar"));
assert!(TopicFilter::new_unchecked("/#").matches("/foo/bar"));
assert!(TopicFilter::new_unchecked("$SYS/bar").matches("$SYS/bar"));
assert!(TopicFilter::new_unchecked("foo/#").matches("foo/$bar"));
assert!(TopicFilter::new_unchecked("foo/+/baz").matches("foo/$bar/baz"));

// Should not match

assert!(!TopicFilter::new_unchecked("test/6/#").matches("test/3"));
assert!(!TopicFilter::new_unchecked("foo/bar").matches("foo"));
assert!(!TopicFilter::new_unchecked("foo/+").matches("foo/bar/baz"));
assert!(!TopicFilter::new_unchecked("foo/+/baz").matches("foo/bar/bar"));
assert!(!TopicFilter::new_unchecked("foo/+/#").matches("fo2/bar/baz"));
assert!(!TopicFilter::new_unchecked("/#").matches("foo/bar"));
assert!(!TopicFilter::new_unchecked("#").matches("$SYS/bar"));
assert!(!TopicFilter::new_unchecked("$BOB/bar").matches("$SYS/bar"));
assert!(!TopicFilter::new_unchecked("+/bar").matches("$SYS/bar"));
}
}
Loading

0 comments on commit a08a941

Please sign in to comment.