use std::collections::HashSet;
use std::fmt;
use super::event::{MAX_TOPIC_BYTES, Topic, TopicError, validate_prefix};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SubscriptionPattern {
Exact(String),
Prefix(String),
}
impl SubscriptionPattern {
pub fn parse(pattern: &str) -> Result<Self, TopicError> {
match pattern.strip_suffix(".*") {
Some(prefix) => {
if pattern.len() > MAX_TOPIC_BYTES {
return Err(TopicError::TooLong {
len: pattern.len(),
max: MAX_TOPIC_BYTES,
});
}
validate_prefix(prefix)?;
Ok(Self::Prefix(format!("{prefix}.")))
}
None => Ok(Self::Exact(Topic::internal(pattern)?.as_str().to_owned())),
}
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "part of the bus API that the IPC server does not use"
)
)]
pub fn matches(&self, topic: &Topic) -> bool {
match self {
Self::Exact(name) => name == topic.as_str(),
Self::Prefix(prefix) => topic.as_str().starts_with(prefix.as_str()),
}
}
}
impl fmt::Display for SubscriptionPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Exact(name) => f.write_str(name),
Self::Prefix(prefix) => write!(f, "{prefix}*"),
}
}
}
#[derive(Debug, Default)]
pub(super) struct SubscriptionSet {
exact: HashSet<String>,
prefixes: HashSet<String>,
}
impl SubscriptionSet {
pub(super) fn insert(&mut self, pattern: SubscriptionPattern) -> bool {
match pattern {
SubscriptionPattern::Exact(name) => self.exact.insert(name),
SubscriptionPattern::Prefix(prefix) => self.prefixes.insert(prefix),
}
}
pub(super) fn contains(&self, pattern: &SubscriptionPattern) -> bool {
match pattern {
SubscriptionPattern::Exact(name) => self.exact.contains(name),
SubscriptionPattern::Prefix(prefix) => self.prefixes.contains(prefix),
}
}
pub(super) fn remove(&mut self, pattern: &SubscriptionPattern) -> bool {
match pattern {
SubscriptionPattern::Exact(name) => self.exact.remove(name),
SubscriptionPattern::Prefix(prefix) => self.prefixes.remove(prefix),
}
}
pub(super) fn len(&self) -> usize {
self.exact.len() + self.prefixes.len()
}
pub(super) fn is_empty(&self) -> bool {
self.len() == 0
}
pub(super) fn matches(&self, topic: &Topic) -> bool {
let name = topic.as_str();
if self.exact.contains(name) {
return true;
}
if self.prefixes.is_empty() {
return false;
}
name.match_indices('.')
.any(|(i, _)| self.prefixes.contains(&name[..=i]))
}
}