use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Topic(String);
impl Topic {
pub fn new(topic: impl Into<String>) -> Self {
Self(topic.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_global_wildcard(&self) -> bool {
self.0 == "*"
}
pub fn matches(&self, topic: &Topic) -> bool {
self.matches_str(topic.as_str())
}
pub fn matches_str(&self, target: &str) -> bool {
let pattern = &self.0;
if pattern == "*" {
return true;
}
if pattern == target {
return true;
}
if !pattern.contains('*') {
return false;
}
let mut pattern_parts = pattern.split('.');
let mut target_parts = target.split('.');
loop {
match (pattern_parts.next(), target_parts.next()) {
(Some(p), Some(t)) => {
if p != "*" && p != t {
return false;
}
}
(None, None) => return true,
_ => return false, }
}
}
}
impl From<&str> for Topic {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for Topic {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl std::fmt::Display for Topic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exact_match() {
let pattern = Topic::new("impl.done");
let target = Topic::new("impl.done");
assert!(pattern.matches(&target));
}
#[test]
fn test_no_match() {
let pattern = Topic::new("impl.done");
let target = Topic::new("review.done");
assert!(!pattern.matches(&target));
}
#[test]
fn test_wildcard_suffix() {
let pattern = Topic::new("impl.*");
assert!(pattern.matches(&Topic::new("impl.done")));
assert!(pattern.matches(&Topic::new("impl.started")));
assert!(!pattern.matches(&Topic::new("review.done")));
}
#[test]
fn test_wildcard_prefix() {
let pattern = Topic::new("*.done");
assert!(pattern.matches(&Topic::new("impl.done")));
assert!(pattern.matches(&Topic::new("review.done")));
assert!(!pattern.matches(&Topic::new("impl.started")));
}
#[test]
fn test_global_wildcard() {
let pattern = Topic::new("*");
assert!(pattern.matches(&Topic::new("impl.done")));
assert!(pattern.matches(&Topic::new("anything")));
}
#[test]
fn test_length_mismatch() {
let pattern = Topic::new("impl.*");
assert!(!pattern.matches(&Topic::new("impl.sub.done")));
}
}