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}