Skip to main content

mqtt_topic_engine/
error.rs

1//! Error types and utilities for the topic module
2//!
3//! This module contains the composite error type and shared constants
4//! for the entire topic module, while individual error types remain
5//! in their respective modules.
6
7use 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/// Comprehensive error type for all topic-related operations
17///
18/// This enum aggregates all possible errors that can occur within the topic module,
19/// providing a single error type for the public API while maintaining detailed
20/// error information from each submodule.
21#[derive(Error, Debug, Clone, PartialEq, Eq)]
22pub enum TopicError {
23	/// Topic pattern parsing or validation error
24	#[error("Topic pattern error: {0}")]
25	Pattern(#[from] TopicPatternError),
26
27	/// Topic formatting error when substituting parameters
28	#[error("Topic format error: {0}")]
29	Format(#[from] TopicFormatError),
30
31	/// Topic matching operation error
32	#[cfg(feature = "router")]
33	#[error("Topic matcher error: {0}")]
34	Matcher(#[from] TopicMatcherError),
35
36	/// Topic routing operation error
37	#[cfg(feature = "router")]
38	#[error("Topic router error: {0}")]
39	Router(#[from] TopicRouterError),
40}
41
42/// Convenient Result type for topic operations
43pub type TopicResult<T> = Result<T, TopicError>;
44
45/// Convenient Result type for pattern operations
46pub type PatternResult<T> = Result<T, TopicPatternError>;
47
48/// Convenient Result type for matcher operations
49#[cfg(feature = "router")]
50pub type MatcherResult<T> = Result<T, TopicMatcherError>;
51
52/// Convenient Result type for router operations
53#[cfg(feature = "router")]
54pub type RouterResult<T> = Result<T, TopicRouterError>;
55
56/// Topic processing limits and constants
57pub mod limits {
58	/// Maximum topic nesting depth allowed
59	pub const MAX_TOPIC_DEPTH: usize = 32;
60
61	/// Maximum length of a single topic segment
62	pub const MAX_SEGMENT_LENGTH: usize = 256;
63
64	/// Maximum total topic path length
65	pub const MAX_TOPIC_LENGTH: usize = 1024;
66}
67
68/// Validation utilities for topic operations
69pub mod validation {
70	#[cfg(feature = "router")]
71	use super::TopicMatcherError;
72	use super::TopicPatternError;
73	use super::limits::*;
74
75	/// Validates topic path for basic constraints
76	#[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	/// Validates topic pattern for subscription constraints
124	pub fn validate_pattern_for_subscription(
125		pattern: &str,
126	) -> Result<(), TopicPatternError> {
127		// Basic validation first
128		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}