use anyhow::Result;
use std::fmt;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathType {
Service,
Action,
Event,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathSegment {
Literal(String),
Template(String),
SingleWildcard,
MultiWildcard,
}
impl PathSegment {
fn from_str(segment: &str) -> Self {
match segment {
"*" => Self::SingleWildcard,
">" => Self::MultiWildcard,
s if s.starts_with('{') && s.ends_with('}') => {
let param_name = &s[1..s.len() - 1];
Self::Template(param_name.to_string())
}
_ => Self::Literal(segment.to_string()),
}
}
pub fn is_wildcard(&self) -> bool {
matches!(self, Self::SingleWildcard | Self::MultiWildcard)
}
pub fn is_multi_wildcard(&self) -> bool {
matches!(self, Self::MultiWildcard)
}
pub fn is_template(&self) -> bool {
matches!(self, Self::Template(_))
}
fn _as_str(&self) -> &str {
match self {
Self::Literal(s) => s.as_str(),
Self::SingleWildcard => "*",
Self::MultiWildcard => ">",
Self::Template(_) => "*", }
}
}
impl fmt::Display for PathSegment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathSegment::Literal(s) => f.write_str(s),
PathSegment::Template(name) => write!(f, "{{{name}}}"),
PathSegment::SingleWildcard => f.write_str("*"),
PathSegment::MultiWildcard => f.write_str(">"),
}
}
}
#[derive(Debug, Clone)]
pub struct TopicPath {
path: String,
network_id: String,
segments: Vec<PathSegment>,
is_pattern: bool,
has_templates: bool,
service_path: String,
cached_action_path: String,
segment_count: usize,
hash_components: Vec<u64>,
segment_type_bitmap: u64,
}
impl Hash for TopicPath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.network_id.hash(state);
if !self.hash_components.is_empty() {
for component in &self.hash_components {
state.write_u64(*component);
}
return;
}
for segment in &self.segments {
match segment {
PathSegment::Literal(s) => {
0.hash(state);
s.hash(state);
}
PathSegment::Template(s) => {
3.hash(state);
s.hash(state);
}
PathSegment::SingleWildcard => {
1.hash(state);
}
PathSegment::MultiWildcard => {
2.hash(state);
}
}
}
}
}
impl PartialEq for TopicPath {
fn eq(&self, other: &Self) -> bool {
if self.path == other.path {
return true;
}
if self.network_id != other.network_id {
return false;
}
if self.segment_count != other.segment_count {
return false;
}
for (s1, s2) in self.segments.iter().zip(other.segments.iter()) {
if s1 != s2 {
return false;
}
}
true
}
}
impl Eq for TopicPath {}
impl std::fmt::Display for TopicPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path)
}
}
impl TopicPath {
pub fn new_action_topic(&self, action_name: &str) -> Result<Self, String> {
if self.segments.len() > 1 {
return Err(
"Invalid action path - cannot create an action path on top of another action path"
.to_string(),
);
}
let full_path_string = format!("{}:{}/{}", self.network_id, self.service_path, action_name);
TopicPath::new(&full_path_string, &self.network_id)
}
pub fn new_event_topic(&self, event_name: &str) -> Result<Self, String> {
let path = self.new_action_topic(event_name)?;
Ok(path)
}
pub fn from_full_path(path: &str) -> Result<Self, String> {
if path.contains(':') {
let parts: Vec<&str> = path.split(':').collect();
if parts.len() != 2 {
return Err(format!(
"Invalid path format - should be 'network_id:service_path' received: {path}"
));
}
if parts[0].is_empty() {
return Err(format!(
"Invalid path format - network ID cannot be empty received: {path}"
));
}
let network_id = parts[0];
let path_without_network = parts[1];
Self::new(path_without_network, network_id)
} else {
Err(format!(
"Invalid path format - missing network_id received: {path}"
))
}
}
pub fn new(path: &str, default_network: &str) -> Result<Self, String> {
let (network_id, path_without_network) = if path.contains(':') {
let parts: Vec<&str> = path.split(':').collect();
if parts.len() != 2 {
return Err(format!("Invalid path format - should be 'network_id:service_path' or 'service_path': {path}"));
}
if parts[0].is_empty() {
return Err(format!("Network ID cannot be empty: {path}"));
}
(parts[0], parts[1])
} else {
(default_network, path)
};
let path_segments: Vec<&str> = path_without_network
.split('/')
.filter(|s| !s.is_empty())
.collect();
if path_segments.is_empty() {
return Err(format!(
"Invalid path - must have at least one segment: {path}"
));
}
let mut segments = Vec::with_capacity(path_segments.len());
let mut is_pattern = false;
let mut has_templates = false;
let mut _multi_wildcard_found = false;
let mut hash_components = Vec::with_capacity(path_segments.len());
let mut segment_type_bitmap: u64 = 0;
for (i, segment_str) in path_segments.iter().enumerate() {
let segment = PathSegment::from_str(segment_str);
match &segment {
PathSegment::SingleWildcard => {
is_pattern = true;
Self::set_segment_type(
&mut segment_type_bitmap,
i,
Self::SEGMENT_TYPE_SINGLE_WILDCARD,
);
}
PathSegment::MultiWildcard => {
is_pattern = true;
_multi_wildcard_found = true;
if i < path_segments.len() - 1 {
return Err(
"Multi-segment wildcard (>) must be the last segment in a path"
.to_string(),
);
}
Self::set_segment_type(
&mut segment_type_bitmap,
i,
Self::SEGMENT_TYPE_MULTI_WILDCARD,
);
}
PathSegment::Template(_) => {
has_templates = true;
Self::set_segment_type(
&mut segment_type_bitmap,
i,
Self::SEGMENT_TYPE_TEMPLATE,
);
}
PathSegment::Literal(_) => {
Self::set_segment_type(&mut segment_type_bitmap, i, Self::SEGMENT_TYPE_LITERAL);
}
}
let mut segment_hasher = std::collections::hash_map::DefaultHasher::new();
match &segment {
PathSegment::Literal(s) => {
0.hash(&mut segment_hasher);
s.hash(&mut segment_hasher);
}
PathSegment::Template(s) => {
3.hash(&mut segment_hasher);
s.hash(&mut segment_hasher);
}
PathSegment::SingleWildcard => {
1.hash(&mut segment_hasher);
}
PathSegment::MultiWildcard => {
2.hash(&mut segment_hasher);
}
}
hash_components.push(segment_hasher.finish());
segments.push(segment);
}
let service_path = match &segments[0] {
PathSegment::Literal(s) => s.clone(),
PathSegment::Template(s) => format!("{{{s}}}"),
PathSegment::SingleWildcard => "*".to_string(),
PathSegment::MultiWildcard => ">".to_string(),
};
let full_path_str = format!("{network_id}:{path_without_network}");
let cached_action_path = if segments.len() <= 1 {
"".to_string()
} else {
segments
.iter()
.map(|segment| segment.to_string())
.collect::<Vec<String>>()
.join("/")
};
let result = Self {
path: full_path_str,
network_id: network_id.to_string(),
segment_count: segments.len(),
segments,
is_pattern,
has_templates,
service_path,
cached_action_path,
hash_components,
segment_type_bitmap,
};
Ok(result)
}
pub fn is_pattern(&self) -> bool {
self.is_pattern
}
pub fn has_multi_wildcard(&self) -> bool {
for i in 0..self.segment_count {
if self.has_segment_type(i, Self::SEGMENT_TYPE_MULTI_WILDCARD) {
return true;
}
}
false
}
pub fn action_path(&self) -> String {
self.cached_action_path.clone()
}
pub fn as_str(&self) -> &str {
&self.path
}
pub fn network_id(&self) -> String {
self.network_id.clone()
}
pub fn service_path(&self) -> String {
self.service_path.clone()
}
pub fn new_service(network_id: &str, service_name: &str) -> Self {
let network_id_string = network_id.to_string();
let service_name_string = service_name.to_string();
let path = format!("{network_id_string}:{service_name_string}");
Self {
path,
network_id: network_id_string,
service_path: service_name_string.clone(),
segments: vec![PathSegment::Literal(service_name_string)],
is_pattern: false,
has_templates: false,
cached_action_path: "".to_string(),
segment_count: 1,
hash_components: Vec::new(),
segment_type_bitmap: 0,
}
}
pub fn starts_with(&self, other: &TopicPath) -> bool {
self.network_id == other.network_id && self.service_path.starts_with(&other.service_path)
}
pub fn child(&self, segment: &str) -> Result<Self, String> {
if segment.contains('/') {
return Err(format!("Child segment cannot contain slashes: {segment}"));
}
let new_path_str = if self.cached_action_path.is_empty() {
format!("{}/{}", self.service_path, segment)
} else {
format!("{}/{}", self.cached_action_path, segment)
};
let full_path_str = format!("{}:{}", self.network_id, new_path_str);
let mut new_segments = self.segments.clone();
let new_segment = PathSegment::from_str(segment);
let is_pattern = self.is_pattern
|| matches!(
new_segment,
PathSegment::SingleWildcard | PathSegment::MultiWildcard
);
let has_templates = self.has_templates || matches!(new_segment, PathSegment::Template(_));
let mut segment_type_bitmap = self.segment_type_bitmap;
match &new_segment {
PathSegment::Literal(_) => {
Self::set_segment_type(
&mut segment_type_bitmap,
self.segment_count,
Self::SEGMENT_TYPE_LITERAL,
);
}
PathSegment::Template(_) => {
Self::set_segment_type(
&mut segment_type_bitmap,
self.segment_count,
Self::SEGMENT_TYPE_TEMPLATE,
);
}
PathSegment::SingleWildcard => {
Self::set_segment_type(
&mut segment_type_bitmap,
self.segment_count,
Self::SEGMENT_TYPE_SINGLE_WILDCARD,
);
}
PathSegment::MultiWildcard => {
Self::set_segment_type(
&mut segment_type_bitmap,
self.segment_count,
Self::SEGMENT_TYPE_MULTI_WILDCARD,
);
}
}
new_segments.push(new_segment);
Ok(Self {
path: full_path_str,
network_id: self.network_id.clone(),
segment_count: self.segment_count + 1,
segments: new_segments,
is_pattern,
has_templates,
service_path: self.service_path.clone(),
cached_action_path: new_path_str,
hash_components: Vec::new(), segment_type_bitmap,
})
}
pub fn get_segments(&self) -> Vec<String> {
self.segments
.iter()
.map(|segment| segment.to_string())
.collect()
}
pub fn parent(&self) -> Result<Self, String> {
if self.segments.len() <= 1 {
return Err("Cannot get parent of root or service-only path".to_string());
}
let parent_segments = self.segments[0..self.segments.len() - 1].to_vec();
let path_str = parent_segments
.iter()
.map(|segment| segment.to_string())
.collect::<Vec<String>>()
.join("/");
let full_path = format!("{}:{}", self.network_id, path_str);
let mut is_pattern = false;
let mut has_templates = false;
let mut segment_type_bitmap = self.segment_type_bitmap;
segment_type_bitmap &= !(0b11 << ((self.segment_count - 1) * 2));
for i in 0..parent_segments.len() {
let segment_type = Self::get_segment_type(segment_type_bitmap, i);
match segment_type {
Self::SEGMENT_TYPE_SINGLE_WILDCARD | Self::SEGMENT_TYPE_MULTI_WILDCARD => {
is_pattern = true;
}
Self::SEGMENT_TYPE_TEMPLATE => {
has_templates = true;
}
_ => {}
}
}
Ok(Self {
path: full_path,
network_id: self.network_id.clone(),
segment_count: self.segment_count - 1,
segments: parent_segments,
is_pattern,
has_templates,
service_path: self.service_path.clone(),
cached_action_path: path_str,
hash_components: Vec::new(), segment_type_bitmap,
})
}
pub fn test_default(path: &str) -> Self {
Self::new(path, "default").unwrap()
}
pub fn extract_params(
&self,
template: &str,
) -> Result<std::collections::HashMap<String, String>, String> {
let mut params = std::collections::HashMap::new();
let path_segments = self.get_segments();
let template_segments: Vec<&str> = template.split('/').filter(|s| !s.is_empty()).collect();
if path_segments.len() != template_segments.len() {
return Err(format!(
"Path segment count ({}) doesn't match template segment count ({})",
path_segments.len(),
template_segments.len()
));
}
for (i, template_segment) in template_segments.iter().enumerate() {
if template_segment.starts_with('{') && template_segment.ends_with('}') {
let param_name = template_segment[1..template_segment.len() - 1].to_string();
params.insert(param_name, path_segments[i].clone());
} else if template_segment != &path_segments[i] {
return Err(format!(
"Path segment '{}' doesn't match template segment '{}'",
path_segments[i], template_segment
));
}
}
Ok(params)
}
pub fn matches_template(&self, template: &str) -> bool {
if !self.has_templates && !template.contains('{') {
return self.path.contains(template);
}
let template_segments: Vec<&str> = template.split('/').filter(|s| !s.is_empty()).collect();
if template_segments.len() != self.segment_count {
return false;
}
let path_segments = self.get_segments();
for (i, template_segment) in template_segments.iter().enumerate() {
if template_segment.starts_with('{') && template_segment.ends_with('}') {
continue;
} else if template_segment != &path_segments[i] {
return false;
}
}
true
}
pub fn has_templates(&self) -> bool {
self.has_templates
}
pub fn segment_count(&self) -> usize {
self.segment_count
}
fn set_segment_type(bitmap: &mut u64, index: usize, segment_type: u8) {
let shift = index * 2;
*bitmap &= !(0b11 << shift);
*bitmap |= (segment_type as u64) << shift;
}
fn get_segment_type(bitmap: u64, index: usize) -> u8 {
((bitmap >> (index * 2)) & 0b11) as u8
}
const SEGMENT_TYPE_LITERAL: u8 = 0b00;
const SEGMENT_TYPE_TEMPLATE: u8 = 0b01;
const SEGMENT_TYPE_SINGLE_WILDCARD: u8 = 0b10;
const SEGMENT_TYPE_MULTI_WILDCARD: u8 = 0b11;
pub fn has_segment_type(&self, index: usize, segment_type: u8) -> bool {
if index >= self.segment_count {
return false;
}
Self::get_segment_type(self.segment_type_bitmap, index) == segment_type
}
pub fn matches(&self, topic: &TopicPath) -> bool {
if self.network_id != topic.network_id {
return false;
}
if self.path == topic.path {
return true;
}
if self.segment_count != topic.segment_count && !self.has_multi_wildcard() {
return false;
}
if !self.is_pattern && !topic.is_pattern && !self.has_templates && !topic.has_templates {
return false;
}
if self.has_templates && !topic.has_templates {
return false;
}
if !self.has_templates && topic.has_templates {
return topic.matches_template(&self.action_path());
}
self.segments_match(&self.segments, &topic.segments)
}
fn segments_match(
&self,
pattern_segments: &[PathSegment],
topic_segments: &[PathSegment],
) -> bool {
if let Some(PathSegment::MultiWildcard) = pattern_segments.last() {
if topic_segments.len() < pattern_segments.len() - 1 {
return false;
}
for i in 0..pattern_segments.len() - 1 {
match &pattern_segments[i] {
PathSegment::Literal(p) => {
match &topic_segments[i] {
PathSegment::Literal(t) if p == t => continue,
_ => return false,
}
}
PathSegment::Template(_) => {
match &topic_segments[i] {
PathSegment::Literal(_) => continue,
_ => return false,
}
}
PathSegment::SingleWildcard => {
continue;
}
PathSegment::MultiWildcard => {
unreachable!("Multi-wildcard found before the end of pattern");
}
}
}
return true;
}
if pattern_segments.len() != topic_segments.len() {
return false;
}
for (i, p) in pattern_segments.iter().enumerate() {
let t = &topic_segments[i];
if p == t {
continue;
}
match p {
PathSegment::Literal(_) => {
return false;
}
PathSegment::Template(_) => {
match t {
PathSegment::Literal(_) => continue,
_ => return false,
}
}
PathSegment::SingleWildcard => {
continue;
}
PathSegment::MultiWildcard => {
return false;
}
}
}
true
}
pub fn from_template(
template_string: &str,
params: std::collections::HashMap<String, String>,
network_id_string: &str,
) -> Result<Self, String> {
let template_segments: Vec<&str> = template_string
.split('/')
.filter(|s| !s.is_empty())
.collect();
let mut path_segments = Vec::new();
for template_segment in template_segments.iter() {
if template_segment.starts_with('{') && template_segment.ends_with('}') {
let param_name = &template_segment[1..template_segment.len() - 1];
match params.get(param_name) {
Some(value) => path_segments.push(PathSegment::Literal(value.clone())),
None => return Err(format!("Missing parameter value for '{param_name}'")),
}
} else {
path_segments.push(PathSegment::Literal(template_segment.to_string()));
}
}
let path_str = path_segments
.iter()
.map(|segment| segment.to_string())
.collect::<Vec<String>>()
.join("/");
Self::new(path_str.as_str(), network_id_string)
}
}
mod path_registry;
pub use path_registry::PathTrie;
pub use path_registry::PathTrieMatch;