Skip to main content

ri/gateway/
routing.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri 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 Ri gateway, using a Radix Tree
23//! for O(k) route lookup performance, where k is the path length.
24//! 
25//! ## Key Components
26//! 
27//! - **RiRouteHandler**: Type alias for route handler functions
28//! - **RiRoute**: Represents a single API route with method, path, handler, and middleware
29//! - **RiRouter**: Manages routes using radix trees for efficient O(k) lookup
30//! 
31//! ## Design Principles
32//! 
33//! 1. **O(k) Performance**: Uses radix tree for route matching independent of route count
34//! 2. **Type Safety**: Uses type aliases for clear handler signatures
35//! 3. **Middleware Support**: Allows attaching middleware to individual routes
36//! 4. **Route Caching**: Caches route matches for improved performance
37//! 5. **Flexible Path Matching**: Supports exact paths, wildcards (`*path`), and path parameters (`:param`)
38//! 6. **Method Support**: Supports all HTTP methods (GET, POST, PUT, DELETE, PATCH, OPTIONS)
39//! 7. **Route Mounting**: Allows mounting routers with prefixes for modularity
40//! 8. **Thread Safe**: Uses RwLock for safe operation in multi-threaded environments
41//! 9. **Async Compatibility**: Built with async/await patterns for modern Rust applications
42//! 
43//! ## Path Pattern Syntax
44//! 
45//! - `/users/:id` - Matches `/users/123`, extracts `id = "123"`
46//! - `/files/*path` - Matches `/files/docs/readme.txt`, extracts `path = "docs/readme.txt"`
47//! - `/api/v1/users` - Exact match for static paths
48//! 
49//! ## Usage
50//! 
51//! ```rust
52//! use ri::prelude::*;
53//! use std::sync::Arc;
54//! 
55//! async fn example() -> RiResult<()> {
56//!     // Create a router
57//!     let router = Arc::new(RiRouter::new());
58//!     
59//!     // Create a simple handler
60//!     let hello_handler = Arc::new(|req| {
61//!         Box::pin(async move {
62//!             Ok(RiGatewayResponse {
63//!                 status_code: 200,
64//!                 headers: HashMap::new(),
65//!                 body: "Hello, Ri!".as_bytes().to_vec(),
66//!             })
67//!         })
68//!     });
69//!     
70//!     // Add routes with O(k) lookup performance
71//!     router.get("/hello", hello_handler.clone());
72//!     router.post("/api/v1/users", hello_handler.clone());
73//!     
74//!     // Add route with path parameter
75//!     router.get("/users/:id", hello_handler.clone());
76//!     
77//!     // Add route with wildcard
78//!     router.get("/files/*path", hello_handler.clone());
79//!     
80//!     // Add route with middleware
81//!     let auth_middleware = Arc::new(RiAuthMiddleware::new("Authorization".to_string()));
82//!     let protected_route = RiRoute::new("GET".to_string(), "/api/v1/protected".to_string(), hello_handler)
83//!         .with_middleware(auth_middleware);
84//!     router.add_route(protected_route);
85//!     
86//!     Ok(())
87//! }
88//! ```
89
90use super::{RiGatewayRequest, RiGatewayResponse};
91use super::radix_tree::RiRadixTree;
92use crate::core::RiResult;
93use crate::core::lock::RwLockExtensions;
94use crate::gateway::middleware::RiMiddleware;
95use std::collections::HashMap;
96use std::fmt;
97use std::future::Future;
98use std::pin::Pin;
99use std::sync::Arc;
100
101#[cfg(feature = "pyo3")]
102use pyo3::PyResult;
103
104/// Type alias for route handler functions.
105/// 
106/// This type represents an asynchronous function that takes a gateway request and returns
107/// a gateway response. It is wrapped in an Arc to allow safe sharing across threads.
108pub type RiRouteHandler = Arc<
109    dyn Fn(RiGatewayRequest) -> Pin<Box<dyn Future<Output = RiResult<RiGatewayResponse>> + Send>>
110        + Send
111        + Sync,
112>;
113
114/// Represents a single API route with method, path, handler, and middleware.
115/// 
116/// This struct encapsulates all the information needed for a single API endpoint,
117/// including the HTTP method, path pattern, request handler, and attached middleware.
118#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
119#[derive(Clone)]
120pub struct RiRoute {
121    /// HTTP method for this route (GET, POST, PUT, DELETE, PATCH, OPTIONS)
122    pub method: String,
123    /// Path pattern for this route (e.g., "/api/v1/users", "/users/:id")
124    pub path: String,
125    /// Request handler for this route
126    pub handler: RiRouteHandler,
127    /// List of middleware attached to this route
128    pub middleware: Vec<Arc<dyn RiMiddleware>>,
129}
130
131impl fmt::Debug for RiRoute {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.debug_struct("RiRoute")
134            .field("method", &self.method)
135            .field("path", &self.path)
136            .field("handler", &"<handler>")
137            .field("middleware_count", &self.middleware.len())
138            .finish()
139    }
140}
141
142impl RiRoute {
143    /// Creates a new route with the specified method, path, and handler.
144    /// 
145    /// # Parameters
146    /// 
147    /// - `method`: HTTP method for this route
148    /// - `path`: Path pattern for this route
149    /// - `handler`: Request handler for this route
150    /// 
151    /// # Returns
152    /// 
153    /// A new `RiRoute` instance with no middleware attached
154    pub fn new(
155        method: String,
156        path: String,
157        handler: RiRouteHandler,
158    ) -> Self {
159        Self {
160            method,
161            path,
162            handler,
163            middleware: Vec::new(),
164        }
165    }
166
167    /// Attaches middleware to this route.
168    /// 
169    /// This method returns a new route instance with the middleware added to the list.
170    /// 
171    /// # Parameters
172    /// 
173    /// - `middleware`: Middleware to attach to this route
174    /// 
175    /// # Returns
176    /// 
177    /// A new `RiRoute` instance with the middleware attached
178    pub fn with_middleware(mut self, middleware: Arc<dyn RiMiddleware>) -> Self {
179        self.middleware.push(middleware);
180        self
181    }
182}
183
184#[cfg(feature = "pyo3")]
185/// Python bindings for RiRoute
186#[pyo3::prelude::pymethods]
187impl RiRoute {
188    #[new]
189    fn py_new(method: String, path: String) -> PyResult<Self> {
190        use crate::gateway::{RiGatewayRequest, RiGatewayResponse};
191        
192        // Create a simple default handler for Python usage
193        let handler = Arc::new(|_req: RiGatewayRequest| -> Pin<Box<dyn Future<Output = Result<RiGatewayResponse, crate::core::RiError>> + Send>> {
194            Box::pin(async move {
195                Ok(RiGatewayResponse {
196                    status_code: 200,
197                    headers: std::collections::HashMap::new(),
198                    body: b"Hello from Ri Python!".to_vec(),
199                    request_id: String::new(),
200                })
201            })
202        });
203        
204        Ok(Self {
205            method,
206            path,
207            handler,
208            middleware: Vec::new(),
209        })
210    }
211}
212
213/// Router for managing API routes and matching requests to handlers.
214/// 
215/// This struct maintains a collection of routes organized by HTTP method using
216/// radix trees for O(k) lookup performance, where k is the path length.
217#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
218pub struct RiRouter {
219    /// Radix trees for each HTTP method
220    trees: std::sync::RwLock<HashMap<String, RiRadixTree>>,
221    /// Vector of registered routes for backward compatibility and introspection
222    routes: std::sync::RwLock<Vec<RiRoute>>,
223    /// Cache of route matches for improved performance
224    route_cache: std::sync::RwLock<HashMap<String, RiRoute>>,
225}
226
227impl Default for RiRouter {
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233impl RiRouter {
234    /// Creates a new router with no routes.
235    /// 
236    /// # Returns
237    /// 
238    /// A new `RiRouter` instance with empty routes and cache
239    pub fn new() -> Self {
240        Self {
241            trees: std::sync::RwLock::new(HashMap::new()),
242            routes: std::sync::RwLock::new(Vec::new()),
243            route_cache: std::sync::RwLock::new(HashMap::new()),
244        }
245    }
246
247    /// Gets or creates a radix tree for the given HTTP method.
248    /// 
249    /// # Parameters
250    /// 
251    /// - `method`: The HTTP method
252    /// 
253    /// # Returns
254    /// 
255    /// A reference to the radix tree for the method
256    #[allow(dead_code)]
257    fn get_or_create_tree(&self, method: &str) -> RiRadixTree {
258        let trees = match self.trees.read_safe("trees for get_or_create") {
259            Ok(t) => t,
260            Err(_) => return RiRadixTree::new(),
261        };
262        
263        if let Some(_tree) = trees.get(method) {
264            return RiRadixTree::new();
265        }
266        
267        drop(trees);
268        
269        let mut trees_mut = match self.trees.write_safe("trees for create") {
270            Ok(t) => t,
271            Err(_) => return RiRadixTree::new(),
272        };
273        
274        let tree = RiRadixTree::new();
275        trees_mut.insert(method.to_string(), RiRadixTree::new());
276        tree
277    }
278
279    /// Adds a route to the router.
280    /// 
281    /// This method adds a route to the router's radix tree and clears the route cache.
282    /// 
283    /// # Parameters
284    /// 
285    /// - `route`: The route to add to the router
286    pub fn add_route(&self, route: RiRoute) {
287        let method = route.method.clone();
288        
289        let mut trees = match self.trees.write_safe("trees for add_route") {
290            Ok(t) => t,
291            Err(e) => {
292                log::error!("Failed to acquire trees write lock: {}", e);
293                return;
294            }
295        };
296        
297        let tree = trees.entry(method).or_insert_with(RiRadixTree::new);
298        tree.insert(route.clone());
299        
300        drop(trees);
301        
302        let mut routes = match self.routes.write_safe("routes for add_route") {
303            Ok(r) => r,
304            Err(e) => {
305                log::error!("Failed to acquire routes write lock: {}", e);
306                return;
307            }
308        };
309        routes.push(route);
310        
311        let mut cache = match self.route_cache.write_safe("cache for add_route") {
312            Ok(c) => c,
313            Err(e) => {
314                log::error!("Failed to acquire cache write lock: {}", e);
315                return;
316            }
317        };
318        cache.clear();
319    }
320}
321
322#[cfg(feature = "pyo3")]
323/// Python bindings for RiRouter
324#[pyo3::prelude::pymethods]
325impl RiRouter {
326    #[new]
327    fn py_new() -> Self {
328        Self::new()
329    }
330    
331    /// Adds a GET route to the router from Python
332    fn add_get_route(&self, path: String) {
333        let route = RiRoute::py_new("GET".to_string(), path).expect("Failed to create route");
334        self.add_route(route);
335    }
336    
337    /// Adds a POST route to the router from Python
338    fn add_post_route(&self, path: String) {
339        let route = RiRoute::py_new("POST".to_string(), path).expect("Failed to create route");
340        self.add_route(route);
341    }
342    
343    /// Adds a PUT route to the router from Python
344    fn add_put_route(&self, path: String) {
345        let route = RiRoute::py_new("PUT".to_string(), path).expect("Failed to create route");
346        self.add_route(route);
347    }
348    
349    /// Adds a DELETE route to the router from Python
350    fn add_delete_route(&self, path: String) {
351        let route = RiRoute::py_new("DELETE".to_string(), path).expect("Failed to create route");
352        self.add_route(route);
353    }
354    
355    /// Adds a PATCH route to the router from Python
356    fn add_patch_route(&self, path: String) {
357        let route = RiRoute::py_new("PATCH".to_string(), path).expect("Failed to create route");
358        self.add_route(route);
359    }
360    
361    /// Adds an OPTIONS route to the router from Python
362    fn add_options_route(&self, path: String) {
363        let route = RiRoute::py_new("OPTIONS".to_string(), path).expect("Failed to create route");
364        self.add_route(route);
365    }
366    
367    /// Adds a custom route to the router from Python
368    fn add_custom_route(&self, method: String, path: String) {
369        let route = RiRoute::py_new(method, path).expect("Failed to create route");
370        self.add_route(route);
371    }
372    
373    /// Gets the number of routes registered in the router
374    fn get_route_count(&self) -> usize {
375        self.route_count()
376    }
377    
378    /// Clears all routes from the router
379    fn clear_all_routes(&self) {
380        self.clear_routes();
381    }
382}
383
384impl RiRouter {
385
386    /// Adds a GET route to the router.
387    /// 
388    /// # Parameters
389    /// 
390    /// - `path`: Path pattern for the route
391    /// - `handler`: Request handler for the route
392    pub fn get(&self, path: &str, handler: RiRouteHandler) {
393        let route = RiRoute::new("GET".to_string(), path.to_string(), handler);
394        self.add_route(route);
395    }
396
397    /// Adds a POST route to the router.
398    /// 
399    /// # Parameters
400    /// 
401    /// - `path`: Path pattern for the route
402    /// - `handler`: Request handler for the route
403    pub fn post(&self, path: &str, handler: RiRouteHandler) {
404        let route = RiRoute::new("POST".to_string(), path.to_string(), handler);
405        self.add_route(route);
406    }
407
408    /// Adds a PUT route to the router.
409    /// 
410    /// # Parameters
411    /// 
412    /// - `path`: Path pattern for the route
413    /// - `handler`: Request handler for the route
414    pub fn put(&self, path: &str, handler: RiRouteHandler) {
415        let route = RiRoute::new("PUT".to_string(), path.to_string(), handler);
416        self.add_route(route);
417    }
418
419    /// Adds a DELETE route to the router.
420    /// 
421    /// # Parameters
422    /// 
423    /// - `path`: Path pattern for the route
424    /// - `handler`: Request handler for the route
425    pub fn delete(&self, path: &str, handler: RiRouteHandler) {
426        let route = RiRoute::new("DELETE".to_string(), path.to_string(), handler);
427        self.add_route(route);
428    }
429
430    /// Adds a PATCH route to the router.
431    /// 
432    /// # Parameters
433    /// 
434    /// - `path`: Path pattern for the route
435    /// - `handler`: Request handler for the route
436    pub fn patch(&self, path: &str, handler: RiRouteHandler) {
437        let route = RiRoute::new("PATCH".to_string(), path.to_string(), handler);
438        self.add_route(route);
439    }
440
441    /// Adds an OPTIONS route to the router.
442    /// 
443    /// # Parameters
444    /// 
445    /// - `path`: Path pattern for the route
446    /// - `handler`: Request handler for the route
447    pub fn options(&self, path: &str, handler: RiRouteHandler) {
448        let route = RiRoute::new("OPTIONS".to_string(), path.to_string(), handler);
449        self.add_route(route);
450    }
451
452    /// Finds a matching route for the given request using radix tree.
453    /// 
454    /// This method uses radix tree for O(k) route lookup where k is the path length.
455    /// It checks the route cache first, then searches the radix tree for the matching route.
456    /// 
457    /// # Parameters
458    /// 
459    /// - `request`: The gateway request to find a route for
460    /// 
461    /// # Returns
462    /// 
463    /// A `RiResult<RiRouteHandler>` with the matching handler, or an error if no route is found
464    pub async fn route(&self, request: &RiGatewayRequest) -> RiResult<RiRouteHandler> {
465        let cache_key = format!("{}:{}", request.method, request.path);
466        
467        {
468            let cache = match self.route_cache.read_safe("cache for route lookup") {
469                Ok(c) => c,
470                Err(_) => return Err(crate::core::RiError::InvalidState("Failed to acquire cache read lock".to_string())),
471            };
472            if let Some(cached_route) = cache.get(&cache_key) {
473                return Ok(cached_route.handler.clone());
474            }
475        }
476
477        let trees = match self.trees.read_safe("trees for route lookup") {
478            Ok(t) => t,
479            Err(_) => return Err(crate::core::RiError::InvalidState("Failed to acquire trees read lock".to_string())),
480        };
481        
482        if let Some(tree) = trees.get(&request.method) {
483            if let Some(route_match) = tree.find(&request.path) {
484                let mut cache = match self.route_cache.write_safe("cache for route insert") {
485                    Ok(c) => c,
486                    Err(_) => return Ok(route_match.route.handler.clone()),
487                };
488                cache.insert(cache_key.clone(), route_match.route.clone());
489                
490                return Ok(route_match.route.handler.clone());
491            }
492        }
493
494        Err(crate::core::RiError::Other(format!(
495            "No route found for {} {}",
496            request.method, request.path
497        )))
498    }
499
500    /// Checks if a route matches a request.
501    /// 
502    /// This method implements route matching logic using the radix tree.
503    /// 
504    /// # Parameters
505    /// 
506    /// - `route_method`: HTTP method of the route
507    /// - `route_path`: Path pattern of the route
508    /// - `request_method`: HTTP method of the request
509    /// - `request_path`: Path of the request
510    /// 
511    /// # Returns
512    /// 
513    /// `true` if the route matches the request, `false` otherwise
514    #[allow(dead_code)]
515    fn matches_route(&self, route_method: &str, route_path: &str, request_method: &str, request_path: &str) -> bool {
516        if route_method != request_method {
517            return false;
518        }
519
520        let trees = match self.trees.read_safe("trees for matches_route") {
521            Ok(t) => t,
522            Err(_) => return false,
523        };
524        
525        if let Some(tree) = trees.get(route_method) {
526            if let Some(route_match) = tree.find(request_path) {
527                return route_match.route.path == route_path;
528            }
529        }
530
531        false
532    }
533
534    /// Mounts another router's routes with a prefix.
535    /// 
536    /// This method adds all routes from another router to this router, prepending
537    /// the specified prefix to each route's path.
538    /// 
539    /// # Parameters
540    /// 
541    /// - `prefix`: The prefix to prepend to all mounted routes
542    /// - `router`: The router to mount
543    pub fn mount(&self, prefix: &str, router: &RiRouter) {
544        let routes = match router.routes.read_safe("routes for mount") {
545            Ok(r) => r,
546            Err(_) => return,
547        };
548        for route in routes.iter() {
549            let mounted_path = if prefix.ends_with('/') && route.path.starts_with('/') {
550                format!("{}{}", prefix, &route.path[1..])
551            } else if !prefix.ends_with('/') && !route.path.starts_with('/') {
552                format!("{}/{}", prefix, route.path)
553            } else {
554                format!("{}{}", prefix, route.path)
555            };
556
557            let mut mounted_route = route.clone();
558            mounted_route.path = mounted_path;
559            self.add_route(mounted_route);
560        }
561    }
562
563    /// Clears all routes from the router.
564    /// 
565    /// This method removes all routes from the router, clears all radix trees, and clears the route cache.
566    pub fn clear_routes(&self) {
567        let trees = match self.trees.write_safe("trees for clear") {
568            Ok(t) => t,
569            Err(e) => {
570                log::error!("Failed to acquire trees write lock: {}", e);
571                return;
572            }
573        };
574        for tree in trees.values() {
575            tree.clear();
576        }
577        drop(trees);
578        
579        let mut routes = match self.routes.write_safe("routes for clear") {
580            Ok(r) => r,
581            Err(e) => {
582                log::error!("Failed to acquire routes write lock: {}", e);
583                return;
584            }
585        };
586        let mut cache = match self.route_cache.write_safe("cache for clear") {
587            Ok(c) => c,
588            Err(e) => {
589                log::error!("Failed to acquire cache write lock: {}", e);
590                return;
591            }
592        };
593        routes.clear();
594        cache.clear();
595    }
596
597    /// Gets the number of routes registered in the router.
598    /// 
599    /// # Returns
600    /// 
601    /// The number of routes registered in the router
602    pub fn route_count(&self) -> usize {
603        match self.routes.read_safe("routes for count") {
604            Ok(routes) => routes.len(),
605            Err(_) => 0,
606        }
607    }
608}