rskit_messaging/
router.rs1use std::sync::Arc;
4
5use async_trait::async_trait;
6use rskit_errors::{AppError, AppResult, ErrorCode};
7
8use crate::handler::MessageHandler;
9use crate::message::Message;
10
11pub struct MessageRouter<T: Send + Sync + 'static> {
28 routes: Vec<(String, Arc<dyn MessageHandler<T>>)>,
29 default: Option<Arc<dyn MessageHandler<T>>>,
30}
31
32impl<T: Send + Sync + 'static> MessageRouter<T> {
33 pub fn new() -> Self {
35 Self {
36 routes: Vec::new(),
37 default: None,
38 }
39 }
40
41 #[must_use]
47 pub fn handle(mut self, pattern: &str, handler: Arc<dyn MessageHandler<T>>) -> Self {
48 self.routes.push((pattern.to_string(), handler));
49 self
50 }
51
52 #[must_use]
54 pub fn default_handler(mut self, handler: Arc<dyn MessageHandler<T>>) -> Self {
55 self.default = Some(handler);
56 self
57 }
58
59 pub fn build(self) -> Arc<dyn MessageHandler<T>> {
62 Arc::new(RouterHandler {
63 routes: self.routes,
64 default: self.default,
65 })
66 }
67}
68
69impl<T: Send + Sync + 'static> Default for MessageRouter<T> {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75struct RouterHandler<T: Send + Sync + 'static> {
77 routes: Vec<(String, Arc<dyn MessageHandler<T>>)>,
78 default: Option<Arc<dyn MessageHandler<T>>>,
79}
80
81#[async_trait]
82impl<T: Send + Sync + 'static> MessageHandler<T> for RouterHandler<T> {
83 async fn handle(&self, msg: Message<T>) -> AppResult<()> {
84 for (pattern, handler) in &self.routes {
85 if matches_pattern(pattern, &msg.topic) {
86 return handler.handle(msg).await;
87 }
88 }
89 if let Some(default) = &self.default {
90 return default.handle(msg).await;
91 }
92 Err(AppError::new(
93 ErrorCode::NotFound,
94 format!("no handler for topic '{}'", msg.topic),
95 ))
96 }
97}
98
99fn matches_pattern(pattern: &str, topic: &str) -> bool {
103 if !pattern.contains('*') {
104 return pattern == topic;
105 }
106 wildcard_match(pattern.as_bytes(), topic.as_bytes())
107}
108
109fn wildcard_match(pattern: &[u8], text: &[u8]) -> bool {
111 match (pattern.first(), text.first()) {
112 (None, None) => true,
113 (Some(b'*'), _) => {
114 wildcard_match(&pattern[1..], text)
115 || (!text.is_empty() && wildcard_match(pattern, &text[1..]))
116 }
117 (Some(p), Some(t)) if p == t => wildcard_match(&pattern[1..], &text[1..]),
118 (Some(_), None) => pattern.iter().all(|&b| b == b'*'),
119 (_, Some(_)) => false,
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use std::sync::atomic::{AtomicU32, Ordering};
126
127 use super::*;
128 use crate::handler::FnHandler;
129
130 fn counting_handler(counter: &Arc<AtomicU32>) -> Arc<dyn MessageHandler<String>> {
131 let c = counter.clone();
132 Arc::new(FnHandler::new(move |_msg: Message<String>| {
133 let c = c.clone();
134 async move {
135 c.fetch_add(1, Ordering::SeqCst);
136 Ok(())
137 }
138 }))
139 }
140
141 #[test]
142 fn pattern_exact_match() {
143 assert!(matches_pattern("orders.created", "orders.created"));
144 assert!(!matches_pattern("orders.created", "orders.updated"));
145 }
146
147 #[test]
148 fn pattern_wildcard_suffix() {
149 assert!(matches_pattern("content.*", "content.discovered"));
150 assert!(matches_pattern("content.*", "content.updated"));
151 assert!(matches_pattern("content.*", "content."));
152 assert!(!matches_pattern("content.*", "other.discovered"));
153 }
154
155 #[test]
156 fn pattern_wildcard_prefix() {
157 assert!(matches_pattern("*.events", "user.events"));
158 assert!(matches_pattern("*.events", "system.events"));
159 assert!(!matches_pattern("*.events", "user.logs"));
160 }
161
162 #[test]
163 fn pattern_wildcard_only() {
164 assert!(matches_pattern("*", "anything"));
165 assert!(matches_pattern("*", ""));
166 }
167
168 #[tokio::test]
169 async fn router_exact_match() {
170 let counter = Arc::new(AtomicU32::new(0));
171 let router = MessageRouter::<String>::new()
172 .handle("orders.created", counting_handler(&counter))
173 .build();
174
175 let msg = Message::new("orders.created", "data".to_string());
176 router.handle(msg).await.unwrap();
177 assert_eq!(counter.load(Ordering::SeqCst), 1);
178 }
179
180 #[tokio::test]
181 async fn router_wildcard_match() {
182 let counter = Arc::new(AtomicU32::new(0));
183 let router = MessageRouter::<String>::new()
184 .handle("content.*", counting_handler(&counter))
185 .build();
186
187 let msg = Message::new("content.discovered", "data".to_string());
188 router.handle(msg).await.unwrap();
189 assert_eq!(counter.load(Ordering::SeqCst), 1);
190 }
191
192 #[tokio::test]
193 async fn router_default_handler() {
194 let specific = Arc::new(AtomicU32::new(0));
195 let default = Arc::new(AtomicU32::new(0));
196 let router = MessageRouter::<String>::new()
197 .handle("orders.*", counting_handler(&specific))
198 .default_handler(counting_handler(&default))
199 .build();
200
201 let msg = Message::new("unknown.topic", "data".to_string());
202 router.handle(msg).await.unwrap();
203 assert_eq!(specific.load(Ordering::SeqCst), 0);
204 assert_eq!(default.load(Ordering::SeqCst), 1);
205 }
206
207 #[tokio::test]
208 async fn router_no_match_returns_error() {
209 let router = MessageRouter::<String>::new().build();
210
211 let msg = Message::new("unknown", "data".to_string());
212 let result = router.handle(msg).await;
213 assert!(result.is_err());
214 }
215
216 #[tokio::test]
217 async fn router_first_match_wins() {
218 let first = Arc::new(AtomicU32::new(0));
219 let second = Arc::new(AtomicU32::new(0));
220
221 let router = MessageRouter::<String>::new()
222 .handle("orders.*", counting_handler(&first))
223 .handle("orders.created", counting_handler(&second))
224 .build();
225
226 let msg = Message::new("orders.created", "data".to_string());
227 router.handle(msg).await.unwrap();
228 assert_eq!(first.load(Ordering::SeqCst), 1);
229 assert_eq!(second.load(Ordering::SeqCst), 0);
230 }
231}