Skip to main content

reinhardt_urls/routers/
server_router.rs

1//! Unified Router with hierarchical routing support
2//!
3//! This module provides a unified router that supports:
4//! - **High-performance O(m) route matching** using matchit Radix Tree (m = path length)
5//! - Nested routers with automatic prefix inheritance
6//! - Namespace-based URL reversal
7//! - Middleware and DI context propagation
8//! - Integration with ViewSets, HTTP macro endpoints, and class-based views
9//!
10//! # Performance Characteristics
11//!
12//! The router uses [matchit](https://docs.rs/matchit) for O(m) route matching where m is the path length:
13//! - Route lookup: O(m) - Independent of the number of registered routes
14//! - Route compilation: O(n) - Done once at startup where n is the number of routes
15//! - Memory: Efficient through Radix Tree's prefix sharing
16//!
17//! With 1000+ routes, matchit provides 3-5x better performance compared to naive O(n×m) linear search.
18//!
19//! # Implementation Details
20//!
21//! Each HTTP method has its own matchit router for optimal performance:
22//! - `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`
23//! - Routes are compiled lazily on first access (thread-safe with RwLock)
24//! - Parameters are extracted directly from matchit's Params
25//!
26//! # Module Layout
27//!
28//! The implementation is split across focused submodules to keep each file
29//! small and reviewable:
30//!
31//! - `types`    — `MiddlewareInfo`, `RouteInfo`, `FunctionRoute`, `ViewRoute`,
32//!   `RouteHandler`, `RouteMatch`, and the `join_path` helper
33//! - `builder` — constructors and builder-style configuration
34//!   (`new`, `with_prefix`, `with_namespace`, `with_di_context`,
35//!   `with_middleware`, `exclude`, `mount`, `group`)
36//! - `registration` — route registration entry points
37//!   (`endpoint`, `handler`, `viewset`, `view`, `with_route_middleware`)
38//! - `compile` — matchit compilation and `validate_*` helpers
39//! - `introspection` — read-only accessors, `get_all_routes`,
40//!   `register_all_routes`, `reverse`
41//! - `dispatch` — `resolve`, `match_own_routes_*`, `path_exists_for_any_method`
42//! - `router_impls` — `Debug`, `Default`, `Handler`, `RegisterViewSet`
43//! - `handlers` — ViewSet handler adapters
44//! - `matching` — `path_matches` and `extract_params` utilities
45//! - `global`   — global router registry used by `showurls`
46
47use crate::routers::UrlReverser;
48use matchit::Router as MatchitRouter;
49use reinhardt_di::InjectionContext;
50use reinhardt_middleware::Middleware;
51#[cfg(feature = "viewsets")]
52use std::collections::HashMap;
53use std::sync::{Arc, RwLock};
54
55pub mod global;
56mod handlers;
57mod matching;
58
59mod builder;
60mod compile;
61mod dispatch;
62mod introspection;
63mod registration;
64mod router_impls;
65mod types;
66
67#[cfg(test)]
68mod tests;
69
70use self::types::{FunctionRoute, RouteHandler, ViewRoute};
71
72pub use self::global::{
73	clear_router, get_router, get_router_di_context, is_router_registered,
74	register_di_registrations, register_router, register_router_arc, take_di_registrations,
75};
76pub use self::matching::{extract_params, path_matches};
77pub use self::types::{MiddlewareInfo, RouteInfo};
78
79/// Unified router with hierarchical routing support
80///
81/// Supports multiple API styles:
82/// - HTTP method macro endpoints
83/// - DRF-style: ViewSets with automatic CRUD
84/// - Django-style: Class-based views
85///
86/// # Examples
87///
88/// ```
89/// use reinhardt_urls::routers::ServerRouter;
90/// # use hyper::Method;
91/// # use reinhardt_core::endpoint::EndpointInfo;
92/// # use reinhardt_http::{Handler, Request, Response, Result};
93///
94/// # struct ExportUsers;
95/// # struct Health;
96/// # impl EndpointInfo for ExportUsers {
97/// #     fn path() -> &'static str { "/export/" }
98/// #     fn method() -> Method { Method::GET }
99/// #     fn name() -> &'static str { "users-export" }
100/// # }
101/// # impl EndpointInfo for Health {
102/// #     fn path() -> &'static str { "/health/" }
103/// #     fn method() -> Method { Method::GET }
104/// #     fn name() -> &'static str { "health" }
105/// # }
106/// # #[async_trait::async_trait]
107/// # impl Handler for ExportUsers {
108/// #     async fn handle(&self, _req: Request) -> Result<Response> { Ok(Response::ok()) }
109/// # }
110/// # #[async_trait::async_trait]
111/// # impl Handler for Health {
112/// #     async fn handle(&self, _req: Request) -> Result<Response> { Ok(Response::ok()) }
113/// # }
114///
115/// # async fn example() -> Result<()> {
116/// // Create a users sub-router
117/// let users_router = ServerRouter::new()
118///     .with_namespace("users")
119///     .endpoint(|| ExportUsers);
120///
121/// // Verify users router has namespace
122/// assert_eq!(users_router.namespace(), Some("users"));
123///
124/// // Create root router
125/// let router = ServerRouter::new()
126///     .with_prefix("/api/v1/")
127///     .with_namespace("v1")
128///     .endpoint(|| Health)
129///     .mount("/users/", users_router);
130///
131/// // Verify root router configuration
132/// assert_eq!(router.prefix(), "/api/v1/");
133/// assert_eq!(router.namespace(), Some("v1"));
134///
135/// // Generated URLs:
136/// // /api/v1/health/
137/// // /api/v1/users/export/
138/// # Ok(())
139/// # }
140/// # tokio::runtime::Runtime::new().unwrap().block_on(example()).unwrap();
141/// ```
142pub struct ServerRouter {
143	/// Router's prefix path
144	pub(crate) prefix: String,
145
146	/// Namespace for URL reversal
147	pub(crate) namespace: Option<String>,
148
149	/// Routes defined in this router
150	pub(crate) routes: Vec<crate::routers::Route>,
151
152	/// ViewSet registrations
153	#[cfg(feature = "viewsets")]
154	pub(crate) viewsets: HashMap<String, Arc<dyn reinhardt_views::viewsets::ViewSet>>,
155
156	/// Function-based routes
157	pub(crate) functions: Vec<FunctionRoute>,
158
159	/// Class-based view routes
160	pub(crate) views: Vec<ViewRoute>,
161
162	/// Child routers
163	pub(crate) children: Vec<ServerRouter>,
164
165	/// DI context
166	pub(crate) di_context: Option<Arc<InjectionContext>>,
167
168	/// Middleware-contributed DI singleton registrations that have been
169	/// harvested by [`Self::with_middleware`] but not yet applied. Filled
170	/// when [`Self::with_middleware`] runs before [`Self::with_di_context`];
171	/// drained either by a later [`Self::with_di_context`] call (into that
172	/// context's `SingletonScope`) or, if no context is ever attached, by
173	/// [`Self::register_all_routes`] (into the global deferred-registration
174	/// list). This avoids both the silent drop and the global-list leak
175	/// described in #4426.
176	pub(crate) pending_middleware_di: reinhardt_di::DiRegistrationList,
177
178	/// Middleware stack
179	pub(crate) middleware: Vec<Arc<dyn Middleware>>,
180
181	/// Middleware type information for runtime introspection
182	pub(crate) middleware_names: Vec<MiddlewareInfo>,
183
184	/// Per-middleware exclusion patterns, indexed parallel to `middleware` vec.
185	/// Each entry contains the exclusion path patterns for the corresponding middleware.
186	pub(crate) middleware_exclusions: Vec<Vec<String>>,
187
188	/// URL reverser
189	pub(crate) reverser: UrlReverser,
190
191	/// Matchit router for GET requests (uses RwLock for thread-safe lazy compilation)
192	pub(crate) get_router: RwLock<MatchitRouter<RouteHandler>>,
193
194	/// Matchit router for POST requests
195	pub(crate) post_router: RwLock<MatchitRouter<RouteHandler>>,
196
197	/// Matchit router for PUT requests
198	pub(crate) put_router: RwLock<MatchitRouter<RouteHandler>>,
199
200	/// Matchit router for DELETE requests
201	pub(crate) delete_router: RwLock<MatchitRouter<RouteHandler>>,
202
203	/// Matchit router for PATCH requests
204	pub(crate) patch_router: RwLock<MatchitRouter<RouteHandler>>,
205
206	/// Matchit router for HEAD requests
207	pub(crate) head_router: RwLock<MatchitRouter<RouteHandler>>,
208
209	/// Matchit router for OPTIONS requests
210	pub(crate) options_router: RwLock<MatchitRouter<RouteHandler>>,
211
212	/// Flag indicating if routes have been compiled (uses RwLock for thread-safety)
213	pub(crate) routes_compiled: RwLock<bool>,
214}