dmsc/gateway/routing.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Routing Module
21//!
22//! This module provides a flexible routing system for the DMSC gateway, allowing for
23//! defining API endpoints and their handlers with support for middleware.
24//!
25//! ## Key Components
26//!
27//! - **DMSCRouteHandler**: Type alias for route handler functions
28//! - **DMSCRoute**: Represents a single API route with method, path, handler, and middleware
29//! - **DMSCRouter**: Manages routes, provides route matching, and supports route mounting
30//!
31//! ## Design Principles
32//!
33//! 1. **Type Safety**: Uses type aliases for clear handler signatures
34//! 2. **Middleware Support**: Allows attaching middleware to individual routes
35//! 3. **Route Caching**: Caches route matches for improved performance
36//! 4. **Flexible Path Matching**: Supports exact paths, wildcards, and path parameters
37//! 5. **Method Support**: Supports all HTTP methods (GET, POST, PUT, DELETE, PATCH, OPTIONS)
38//! 6. **Route Mounting**: Allows mounting routers with prefixes for modularity
39//! 7. **Thread Safe**: Uses RwLock for safe operation in multi-threaded environments
40//! 8. **Async Compatibility**: Built with async/await patterns for modern Rust applications
41//!
42//! ## Usage
43//!
44//! ```rust
45//! use dmsc::prelude::*;
46//! use std::sync::Arc;
47//!
48//! async fn example() -> DMSCResult<()> {
49//! // Create a router
50//! let router = Arc::new(DMSCRouter::new());
51//!
52//! // Create a simple handler
53//! let hello_handler = Arc::new(|req| {
54//! Box::pin(async move {
55//! Ok(DMSCGatewayResponse {
56//! status_code: 200,
57//! headers: HashMap::new(),
58//! body: "Hello, DMSC!".as_bytes().to_vec(),
59//! })
60//! })
61//! });
62//!
63//! // Add routes
64//! router.get("/hello", hello_handler.clone());
65//! router.post("/api/v1/users", hello_handler.clone());
66//!
67//! // Add route with middleware
68//! let auth_middleware = Arc::new(DMSCAuthMiddleware::new("Authorization".to_string()));
69//! let protected_route = DMSCRoute::new("GET".to_string(), "/api/v1/protected".to_string(), hello_handler)
70//! .with_middleware(auth_middleware);
71//! router.add_route(protected_route);
72//!
73//! Ok(())
74//! }
75//! ```
76
77use super::{DMSCGatewayRequest, DMSCGatewayResponse};
78use crate::core::DMSCResult;
79use crate::core::lock::RwLockExtensions;
80use crate::gateway::middleware::DMSCMiddleware;
81use std::collections::HashMap;
82use std::future::Future;
83use std::pin::Pin;
84use std::sync::Arc;
85
86#[cfg(feature = "pyo3")]
87use pyo3::PyResult;
88
89/// Type alias for route handler functions.
90///
91/// This type represents an asynchronous function that takes a gateway request and returns
92/// a gateway response. It is wrapped in an Arc to allow safe sharing across threads.
93pub type DMSCRouteHandler = Arc<
94 dyn Fn(DMSCGatewayRequest) -> Pin<Box<dyn Future<Output = DMSCResult<DMSCGatewayResponse>> + Send>>
95 + Send
96 + Sync,
97>;
98
99/// Represents a single API route with method, path, handler, and middleware.
100///
101/// This struct encapsulates all the information needed for a single API endpoint,
102/// including the HTTP method, path pattern, request handler, and attached middleware.
103#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
104#[derive(Clone)]
105pub struct DMSCRoute {
106 /// HTTP method for this route (GET, POST, PUT, DELETE, PATCH, OPTIONS)
107 pub method: String,
108 /// Path pattern for this route (e.g., "/api/v1/users", "/users/:id")
109 pub path: String,
110 /// Request handler for this route
111 pub handler: DMSCRouteHandler,
112 /// List of middleware attached to this route
113 pub middleware: Vec<Arc<dyn DMSCMiddleware>>,
114}
115
116impl DMSCRoute {
117 /// Creates a new route with the specified method, path, and handler.
118 ///
119 /// # Parameters
120 ///
121 /// - `method`: HTTP method for this route
122 /// - `path`: Path pattern for this route
123 /// - `handler`: Request handler for this route
124 ///
125 /// # Returns
126 ///
127 /// A new `DMSCRoute` instance with no middleware attached
128 pub fn new(
129 method: String,
130 path: String,
131 handler: DMSCRouteHandler,
132 ) -> Self {
133 Self {
134 method,
135 path,
136 handler,
137 middleware: Vec::new(),
138 }
139 }
140
141 /// Attaches middleware to this route.
142 ///
143 /// This method returns a new route instance with the middleware added to the list.
144 ///
145 /// # Parameters
146 ///
147 /// - `middleware`: Middleware to attach to this route
148 ///
149 /// # Returns
150 ///
151 /// A new `DMSCRoute` instance with the middleware attached
152 pub fn with_middleware(mut self, middleware: Arc<dyn DMSCMiddleware>) -> Self {
153 self.middleware.push(middleware);
154 self
155 }
156}
157
158#[cfg(feature = "pyo3")]
159/// Python bindings for DMSCRoute
160#[pyo3::prelude::pymethods]
161impl DMSCRoute {
162 #[new]
163 fn py_new(method: String, path: String) -> PyResult<Self> {
164 use crate::gateway::{DMSCGatewayRequest, DMSCGatewayResponse};
165
166 // Create a simple default handler for Python usage
167 let handler = Arc::new(|_req: DMSCGatewayRequest| -> Pin<Box<dyn Future<Output = Result<DMSCGatewayResponse, crate::core::DMSCError>> + Send>> {
168 Box::pin(async move {
169 Ok(DMSCGatewayResponse {
170 status_code: 200,
171 headers: std::collections::HashMap::new(),
172 body: b"Hello from DMSC Python!".to_vec(),
173 request_id: String::new(),
174 })
175 })
176 });
177
178 Ok(Self {
179 method,
180 path,
181 handler,
182 middleware: Vec::new(),
183 })
184 }
185}
186
187/// Router for managing API routes and matching requests to handlers.
188///
189/// This struct maintains a collection of routes and provides methods for adding routes,
190/// matching requests to handlers, and mounting routers with prefixes.
191#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
192pub struct DMSCRouter {
193 /// Vector of registered routes
194 routes: std::sync::RwLock<Vec<DMSCRoute>>,
195 /// Cache of route matches for improved performance
196 route_cache: std::sync::RwLock<HashMap<String, DMSCRoute>>,
197}
198
199impl Default for DMSCRouter {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205impl DMSCRouter {
206 /// Creates a new router with no routes.
207 ///
208 /// # Returns
209 ///
210 /// A new `DMSCRouter` instance with empty routes and cache
211 pub fn new() -> Self {
212 Self {
213 routes: std::sync::RwLock::new(Vec::new()),
214 route_cache: std::sync::RwLock::new(HashMap::new()),
215 }
216 }
217
218 /// Adds a route to the router.
219 ///
220 /// This method adds a route to the router's collection and clears the route cache.
221 ///
222 /// # Parameters
223 ///
224 /// - `route`: The route to add to the router
225 pub fn add_route(&self, route: DMSCRoute) {
226 let mut routes = match self.routes.write_safe("routes for add_route") {
227 Ok(r) => r,
228 Err(e) => {
229 log::error!("Failed to acquire routes write lock: {}", e);
230 return;
231 }
232 };
233 routes.push(route);
234
235 // Clear cache when routes are modified
236 let mut cache = match self.route_cache.write_safe("cache for add_route") {
237 Ok(c) => c,
238 Err(e) => {
239 log::error!("Failed to acquire cache write lock: {}", e);
240 return;
241 }
242 };
243 cache.clear();
244 }
245}
246
247#[cfg(feature = "pyo3")]
248/// Python bindings for DMSCRouter
249#[pyo3::prelude::pymethods]
250impl DMSCRouter {
251 #[new]
252 fn py_new() -> Self {
253 Self::new()
254 }
255
256 /// Adds a GET route to the router from Python
257 fn add_get_route(&self, path: String) {
258 let route = DMSCRoute::py_new("GET".to_string(), path).expect("Failed to create route");
259 self.add_route(route);
260 }
261
262 /// Adds a POST route to the router from Python
263 fn add_post_route(&self, path: String) {
264 let route = DMSCRoute::py_new("POST".to_string(), path).expect("Failed to create route");
265 self.add_route(route);
266 }
267
268 /// Adds a PUT route to the router from Python
269 fn add_put_route(&self, path: String) {
270 let route = DMSCRoute::py_new("PUT".to_string(), path).expect("Failed to create route");
271 self.add_route(route);
272 }
273
274 /// Adds a DELETE route to the router from Python
275 fn add_delete_route(&self, path: String) {
276 let route = DMSCRoute::py_new("DELETE".to_string(), path).expect("Failed to create route");
277 self.add_route(route);
278 }
279
280 /// Adds a PATCH route to the router from Python
281 fn add_patch_route(&self, path: String) {
282 let route = DMSCRoute::py_new("PATCH".to_string(), path).expect("Failed to create route");
283 self.add_route(route);
284 }
285
286 /// Adds an OPTIONS route to the router from Python
287 fn add_options_route(&self, path: String) {
288 let route = DMSCRoute::py_new("OPTIONS".to_string(), path).expect("Failed to create route");
289 self.add_route(route);
290 }
291
292 /// Adds a custom route to the router from Python
293 fn add_custom_route(&self, method: String, path: String) {
294 let route = DMSCRoute::py_new(method, path).expect("Failed to create route");
295 self.add_route(route);
296 }
297
298 /// Gets the number of routes registered in the router
299 fn get_route_count(&self) -> usize {
300 self.route_count()
301 }
302
303 /// Clears all routes from the router
304 fn clear_all_routes(&self) {
305 self.clear_routes();
306 }
307}
308
309impl DMSCRouter {
310
311 /// Adds a GET route to the router.
312 ///
313 /// # Parameters
314 ///
315 /// - `path`: Path pattern for the route
316 /// - `handler`: Request handler for the route
317 pub fn get(&self, path: &str, handler: DMSCRouteHandler) {
318 let route = DMSCRoute::new("GET".to_string(), path.to_string(), handler);
319 self.add_route(route);
320 }
321
322 /// Adds a POST route to the router.
323 ///
324 /// # Parameters
325 ///
326 /// - `path`: Path pattern for the route
327 /// - `handler`: Request handler for the route
328 pub fn post(&self, path: &str, handler: DMSCRouteHandler) {
329 let route = DMSCRoute::new("POST".to_string(), path.to_string(), handler);
330 self.add_route(route);
331 }
332
333 /// Adds a PUT route to the router.
334 ///
335 /// # Parameters
336 ///
337 /// - `path`: Path pattern for the route
338 /// - `handler`: Request handler for the route
339 pub fn put(&self, path: &str, handler: DMSCRouteHandler) {
340 let route = DMSCRoute::new("PUT".to_string(), path.to_string(), handler);
341 self.add_route(route);
342 }
343
344 /// Adds a DELETE route to the router.
345 ///
346 /// # Parameters
347 ///
348 /// - `path`: Path pattern for the route
349 /// - `handler`: Request handler for the route
350 pub fn delete(&self, path: &str, handler: DMSCRouteHandler) {
351 let route = DMSCRoute::new("DELETE".to_string(), path.to_string(), handler);
352 self.add_route(route);
353 }
354
355 /// Adds a PATCH route to the router.
356 ///
357 /// # Parameters
358 ///
359 /// - `path`: Path pattern for the route
360 /// - `handler`: Request handler for the route
361 pub fn patch(&self, path: &str, handler: DMSCRouteHandler) {
362 let route = DMSCRoute::new("PATCH".to_string(), path.to_string(), handler);
363 self.add_route(route);
364 }
365
366 /// Adds an OPTIONS route to the router.
367 ///
368 /// # Parameters
369 ///
370 /// - `path`: Path pattern for the route
371 /// - `handler`: Request handler for the route
372 pub fn options(&self, path: &str, handler: DMSCRouteHandler) {
373 let route = DMSCRoute::new("OPTIONS".to_string(), path.to_string(), handler);
374 self.add_route(route);
375 }
376
377 /// Finds a matching route for the given request.
378 ///
379 /// This method checks the route cache first, then searches through registered routes
380 /// to find a match. It returns the handler for the matching route, or an error if no route is found.
381 ///
382 /// # Parameters
383 ///
384 /// - `request`: The gateway request to find a route for
385 ///
386 /// # Returns
387 ///
388 /// A `DMSCResult<DMSCRouteHandler>` with the matching handler, or an error if no route is found
389 pub async fn route(&self, request: &DMSCGatewayRequest) -> DMSCResult<DMSCRouteHandler> {
390 let cache_key = format!("{}:{}", request.method, request.path);
391
392 // Check cache first
393 {
394 let cache = match self.route_cache.read_safe("cache for route lookup") {
395 Ok(c) => c,
396 Err(_) => return Err(crate::core::DMSCError::InvalidState("Failed to acquire cache read lock".to_string())),
397 };
398 if let Some(cached_route) = cache.get(&cache_key) {
399 return Ok(cached_route.handler.clone());
400 }
401 }
402
403 // Find matching route
404 let routes = match self.routes.read_safe("routes for route lookup") {
405 Ok(r) => r,
406 Err(_) => return Err(crate::core::DMSCError::InvalidState("Failed to acquire routes read lock".to_string())),
407 };
408 for route in routes.iter() {
409 if self.matches_route(&route.method, &route.path, &request.method, &request.path) {
410 // Cache the result
411 let mut cache = match self.route_cache.write_safe("cache for route insert") {
412 Ok(c) => c,
413 Err(_) => return Ok(route.handler.clone()), // Return anyway, cache miss is acceptable
414 };
415 cache.insert(cache_key.clone(), route.clone());
416
417 return Ok(route.handler.clone());
418 }
419 }
420
421 Err(crate::core::DMSCError::Other(format!(
422 "No route found for {} {}",
423 request.method, request.path
424 )))
425 }
426
427 /// Checks if a route matches a request.
428 ///
429 /// This method implements route matching logic, including exact path matching,
430 /// wildcard matching, and basic path parameter matching.
431 ///
432 /// # Parameters
433 ///
434 /// - `route_method`: HTTP method of the route
435 /// - `route_path`: Path pattern of the route
436 /// - `request_method`: HTTP method of the request
437 /// - `request_path`: Path of the request
438 ///
439 /// # Returns
440 ///
441 /// `true` if the route matches the request, `false` otherwise
442 fn matches_route(&self, route_method: &str, route_path: &str, request_method: &str, request_path: &str) -> bool {
443 // Check method
444 if route_method != request_method {
445 return false;
446 }
447
448 // Simple path matching (can be enhanced with proper path parameters)
449 if route_path == request_path {
450 return true;
451 }
452
453 // Handle wildcards
454 if route_path == "*" {
455 return true;
456 }
457
458 // Handle path parameters (basic implementation)
459 if route_path.contains(':') {
460 let route_parts: Vec<&str> = route_path.split('/').collect();
461 let request_parts: Vec<&str> = request_path.split('/').collect();
462
463 if route_parts.len() != request_parts.len() {
464 return false;
465 }
466
467 for (route_part, request_part) in route_parts.iter().zip(request_parts.iter()) {
468 if !route_part.starts_with(':') && route_part != request_part {
469 return false;
470 }
471 }
472
473 return true;
474 }
475
476 false
477 }
478
479 /// Mounts another router's routes with a prefix.
480 ///
481 /// This method adds all routes from another router to this router, prepending
482 /// the specified prefix to each route's path.
483 ///
484 /// # Parameters
485 ///
486 /// - `prefix`: The prefix to prepend to all mounted routes
487 /// - `router`: The router to mount
488 pub fn mount(&self, prefix: &str, router: &DMSCRouter) {
489 let routes = match router.routes.read_safe("routes for mount") {
490 Ok(r) => r,
491 Err(_) => return,
492 };
493 for route in routes.iter() {
494 let mounted_path = if prefix.ends_with('/') && route.path.starts_with('/') {
495 format!("{}{}", prefix, &route.path[1..])
496 } else if !prefix.ends_with('/') && !route.path.starts_with('/') {
497 format!("{}/{}", prefix, route.path)
498 } else {
499 format!("{}{}", prefix, route.path)
500 };
501
502 let mut mounted_route = route.clone();
503 mounted_route.path = mounted_path;
504 self.add_route(mounted_route);
505 }
506 }
507
508 /// Clears all routes from the router.
509 ///
510 /// This method removes all routes from the router and clears the route cache.
511 pub fn clear_routes(&self) {
512 let mut routes = match self.routes.write_safe("routes for clear") {
513 Ok(r) => r,
514 Err(e) => {
515 log::error!("Failed to acquire routes write lock: {}", e);
516 return;
517 }
518 };
519 let mut cache = match self.route_cache.write_safe("cache for clear") {
520 Ok(c) => c,
521 Err(e) => {
522 log::error!("Failed to acquire cache write lock: {}", e);
523 return;
524 }
525 };
526 routes.clear();
527 cache.clear();
528 }
529
530 /// Gets the number of routes registered in the router.
531 ///
532 /// # Returns
533 ///
534 /// The number of routes registered in the router
535 pub fn route_count(&self) -> usize {
536 match self.routes.read_safe("routes for count") {
537 Ok(routes) => routes.len(),
538 Err(_) => 0,
539 }
540 }
541}