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}