use thiserror::Error;
#[cfg(feature = "router")]
use crate::topic_matcher::TopicMatcherError;
use crate::topic_pattern_item::TopicPatternError;
use crate::topic_pattern_path::TopicFormatError;
#[cfg(feature = "router")]
use crate::topic_router::TopicRouterError;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum TopicError {
#[error("Topic pattern error: {0}")]
Pattern(#[from] TopicPatternError),
#[error("Topic format error: {0}")]
Format(#[from] TopicFormatError),
#[cfg(feature = "router")]
#[error("Topic matcher error: {0}")]
Matcher(#[from] TopicMatcherError),
#[cfg(feature = "router")]
#[error("Topic router error: {0}")]
Router(#[from] TopicRouterError),
}
pub type TopicResult<T> = Result<T, TopicError>;
pub type PatternResult<T> = Result<T, TopicPatternError>;
#[cfg(feature = "router")]
pub type MatcherResult<T> = Result<T, TopicMatcherError>;
#[cfg(feature = "router")]
pub type RouterResult<T> = Result<T, TopicRouterError>;
pub mod limits {
pub const MAX_TOPIC_DEPTH: usize = 32;
pub const MAX_SEGMENT_LENGTH: usize = 256;
pub const MAX_TOPIC_LENGTH: usize = 1024;
}
pub mod validation {
#[cfg(feature = "router")]
use super::TopicMatcherError;
use super::TopicPatternError;
use super::limits::*;
#[cfg(feature = "router")]
pub fn validate_topic_path(path: &str) -> Result<(), TopicMatcherError> {
if path.is_empty() {
return Err(TopicMatcherError::EmptyTopicPath);
}
if path.len() > MAX_TOPIC_LENGTH {
return Err(TopicMatcherError::invalid_utf8(format!(
"Topic path too long: {} > {}",
path.len(),
MAX_TOPIC_LENGTH
)));
}
if !path.is_ascii() {
return Err(TopicMatcherError::invalid_utf8(
"Non-ASCII characters in topic path".to_string(),
));
}
let segments: Vec<&str> = path.split('/').collect();
if segments.len() > MAX_TOPIC_DEPTH {
return Err(TopicMatcherError::invalid_segment(
format!("depth-{}", segments.len()),
0,
));
}
for (index, segment) in segments.iter().enumerate() {
if segment.len() > MAX_SEGMENT_LENGTH {
return Err(TopicMatcherError::invalid_segment(
segment.to_string(),
index,
));
}
if segment.contains('\0') {
return Err(TopicMatcherError::invalid_segment(
format!("null-byte-in-{segment}"),
index,
));
}
}
Ok(())
}
pub fn validate_pattern_for_subscription(
pattern: &str,
) -> Result<(), TopicPatternError> {
if pattern.is_empty() || pattern.trim().is_empty() {
return Err(TopicPatternError::EmptyTopic);
}
let segments: Vec<&str> = pattern.split('/').collect();
if segments.len() > MAX_TOPIC_DEPTH {
return Err(TopicPatternError::wildcard_usage(format!(
"Pattern too deep: {} segments > {}",
segments.len(),
MAX_TOPIC_DEPTH
)));
}
Ok(())
}
}