///|
/// Result returned while validating a topic pattern.
pub(all) enum TopicPatternStatus {
PatternValid
PatternEmptySegment(Int)
PatternHashNotLast(Int)
PatternInvalidWildcard(Int, String)
} derive(Eq, Debug)
///|
/// Compiled dot-separated topic pattern.
///
/// `*` consumes exactly one segment and `#` consumes remaining segments.
/// An empty pattern matches every topic for backwards compatibility.
pub(all) struct TopicPattern {
raw : String
segments : Array[String]
status : TopicPatternStatus
} derive(Eq, Debug)
///|
fn owned_segments(value : String) -> Array[String] {
value.split(".").map(part => part.to_owned()).collect()
}
///|
fn validate_pattern_segments(segments : Array[String]) -> TopicPatternStatus {
let mut index = 0
for segment in segments {
if segment == "" {
return PatternEmptySegment(index)
}
if segment == "#" && index != segments.length() - 1 {
return PatternHashNotLast(index)
}
if segment != "*" &&
segment != "#" &&
(segment.contains("*") || segment.contains("#")) {
return PatternInvalidWildcard(index, segment)
}
index = index + 1
}
PatternValid
}
///|
pub fn TopicPattern::compile(raw : String) -> TopicPattern {
if raw == "" {
{ raw, segments: [], status: PatternValid }
} else {
let segments = owned_segments(raw)
{ raw, segments, status: validate_pattern_segments(segments) }
}
}
///|
pub fn TopicPattern::is_valid(self : TopicPattern) -> Bool {
self.status == PatternValid
}
///|
pub fn TopicPattern::is_catch_all(self : TopicPattern) -> Bool {
self.raw == "" || self.raw == "#"
}
///|
pub fn TopicPattern::matches(self : TopicPattern, topic : String) -> Bool {
if !self.is_valid() {
return false
}
if self.raw == "" || self.raw == "#" {
return true
}
let topic_segments = owned_segments(topic)
let mut index = 0
while index < self.segments.length() {
let expected = self.segments[index]
if expected == "#" {
return true
}
if index >= topic_segments.length() {
return false
}
if expected != "*" && expected != topic_segments[index] {
return false
}
index = index + 1
}
index == topic_segments.length()
}
///|
/// Higher specificity means fewer wildcard segments.
pub fn TopicPattern::specificity(self : TopicPattern) -> Int {
if !self.is_valid() {
return -1
}
let mut score = 0
for segment in self.segments {
if segment == "*" {
score = score + 1
} else if segment != "#" {
score = score + 4
}
}
score
}
///|
pub fn topic_matches(pattern : String, topic : String) -> Bool {
TopicPattern::compile(pattern).matches(topic)
}
///|
pub fn validate_topic_pattern(pattern : String) -> TopicPatternStatus {
TopicPattern::compile(pattern).status
}