mqtt_topic_engine/
error.rs1use thiserror::Error;
8
9#[cfg(feature = "router")]
10use crate::topic_matcher::TopicMatcherError;
11use crate::topic_pattern_item::TopicPatternError;
12use crate::topic_pattern_path::TopicFormatError;
13#[cfg(feature = "router")]
14use crate::topic_router::TopicRouterError;
15
16#[derive(Error, Debug, Clone, PartialEq, Eq)]
22pub enum TopicError {
23 #[error("Topic pattern error: {0}")]
25 Pattern(#[from] TopicPatternError),
26
27 #[error("Topic format error: {0}")]
29 Format(#[from] TopicFormatError),
30
31 #[cfg(feature = "router")]
33 #[error("Topic matcher error: {0}")]
34 Matcher(#[from] TopicMatcherError),
35
36 #[cfg(feature = "router")]
38 #[error("Topic router error: {0}")]
39 Router(#[from] TopicRouterError),
40}
41
42pub type TopicResult<T> = Result<T, TopicError>;
44
45pub type PatternResult<T> = Result<T, TopicPatternError>;
47
48#[cfg(feature = "router")]
50pub type MatcherResult<T> = Result<T, TopicMatcherError>;
51
52#[cfg(feature = "router")]
54pub type RouterResult<T> = Result<T, TopicRouterError>;
55
56pub mod limits {
58 pub const MAX_TOPIC_DEPTH: usize = 32;
60
61 pub const MAX_SEGMENT_LENGTH: usize = 256;
63
64 pub const MAX_TOPIC_LENGTH: usize = 1024;
66}
67
68pub mod validation {
70 #[cfg(feature = "router")]
71 use super::TopicMatcherError;
72 use super::TopicPatternError;
73 use super::limits::*;
74
75 #[cfg(feature = "router")]
77 pub fn validate_topic_path(path: &str) -> Result<(), TopicMatcherError> {
78 if path.is_empty() {
79 return Err(TopicMatcherError::EmptyTopicPath);
80 }
81
82 if path.len() > MAX_TOPIC_LENGTH {
83 return Err(TopicMatcherError::invalid_utf8(format!(
84 "Topic path too long: {} > {}",
85 path.len(),
86 MAX_TOPIC_LENGTH
87 )));
88 }
89
90 if !path.is_ascii() {
91 return Err(TopicMatcherError::invalid_utf8(
92 "Non-ASCII characters in topic path".to_string(),
93 ));
94 }
95
96 let segments: Vec<&str> = path.split('/').collect();
97 if segments.len() > MAX_TOPIC_DEPTH {
98 return Err(TopicMatcherError::invalid_segment(
99 format!("depth-{}", segments.len()),
100 0,
101 ));
102 }
103
104 for (index, segment) in segments.iter().enumerate() {
105 if segment.len() > MAX_SEGMENT_LENGTH {
106 return Err(TopicMatcherError::invalid_segment(
107 segment.to_string(),
108 index,
109 ));
110 }
111
112 if segment.contains('\0') {
113 return Err(TopicMatcherError::invalid_segment(
114 format!("null-byte-in-{segment}"),
115 index,
116 ));
117 }
118 }
119
120 Ok(())
121 }
122
123 pub fn validate_pattern_for_subscription(
125 pattern: &str,
126 ) -> Result<(), TopicPatternError> {
127 if pattern.is_empty() || pattern.trim().is_empty() {
129 return Err(TopicPatternError::EmptyTopic);
130 }
131
132 let segments: Vec<&str> = pattern.split('/').collect();
133 if segments.len() > MAX_TOPIC_DEPTH {
134 return Err(TopicPatternError::wildcard_usage(format!(
135 "Pattern too deep: {} segments > {}",
136 segments.len(),
137 MAX_TOPIC_DEPTH
138 )));
139 }
140
141 Ok(())
142 }
143}