1use axum::Router as AxumRouter;
2
3#[non_exhaustive]
5pub struct Router {
6 inner: AxumRouter,
7}
8
9impl Default for Router {
10 fn default() -> Self {
11 Self::new()
12 }
13}
14
15impl Router {
16 pub fn new() -> Self {
18 Router {
19 inner: AxumRouter::new(),
20 }
21 }
22
23 pub fn route<T>(self, path: &str, method_router: T) -> Self
26 where
27 T: Into<axum::routing::MethodRouter>,
28 {
29 Router {
30 inner: self.inner.route(path, method_router.into()),
31 }
32 }
33
34 #[cfg_attr(mutants, mutants::skip)]
37 pub fn ws<H, T>(self, path: &str, handler: H) -> Self
38 where
39 T: 'static,
40 H: axum::handler::Handler<T, ()>,
41 {
42 Router {
43 inner: self.inner.route(path, axum::routing::get(handler)),
44 }
45 }
46
47 pub fn into_axum(self) -> AxumRouter {
50 self.inner
51 }
52
53 pub fn as_axum(&self) -> &AxumRouter {
55 &self.inner
56 }
57
58 pub fn as_axum_mut(&mut self) -> &mut AxumRouter {
60 &mut self.inner
61 }
62
63 pub fn fallback<H, T>(self, handler: H) -> Self
65 where
66 H: axum::handler::Handler<T, ()>,
67 T: 'static,
68 {
69 Router {
70 inner: self.inner.fallback(handler),
71 }
72 }
73
74 pub fn fallback_service<S>(self, service: S) -> Self
76 where
77 S: tower_service::Service<
78 axum::extract::Request,
79 Response = axum::response::Response,
80 Error = std::convert::Infallible,
81 > + Clone
82 + Send
83 + Sync
84 + 'static,
85 S::Future: Send + 'static,
86 {
87 Router {
88 inner: self.inner.fallback_service(service),
89 }
90 }
91
92 pub fn nest(self, path: &str, router: Router) -> Self {
95 Router {
96 inner: self.inner.nest(path, router.inner),
97 }
98 }
99
100 pub fn nest_axum(self, path: &str, router: AxumRouter) -> Self {
103 Router {
104 inner: self.inner.nest(path, router),
105 }
106 }
107
108 pub fn merge_axum(self, router: AxumRouter) -> Self {
115 Router {
116 inner: self.inner.merge(router),
117 }
118 }
119
120 #[cfg_attr(mutants, mutants::skip)]
123 pub fn layer<L>(self, layer: L) -> Self
124 where
125 L: tower_layer::Layer<axum::routing::Route> + Clone + Send + Sync + 'static,
126 L::Service: tower_service::Service<
127 axum::extract::Request,
128 Response = axum::response::Response,
129 Error = std::convert::Infallible,
130 > + Clone
131 + Send
132 + Sync
133 + 'static,
134 <L::Service as tower_service::Service<axum::extract::Request>>::Future: Send + 'static,
135 {
136 Router {
137 inner: self.inner.layer(layer),
138 }
139 }
140}
141
142impl From<AxumRouter> for Router {
143 fn from(inner: AxumRouter) -> Self {
144 Router { inner }
145 }
146}
147
148impl From<Router> for AxumRouter {
149 fn from(router: Router) -> Self {
150 router.inner
151 }
152}
153
154impl std::ops::Deref for Router {
155 type Target = AxumRouter;
156
157 fn deref(&self) -> &Self::Target {
158 &self.inner
159 }
160}
161
162impl std::ops::DerefMut for Router {
163 fn deref_mut(&mut self) -> &mut Self::Target {
164 &mut self.inner
165 }
166}
167
168pub use axum::routing::delete;
169pub use axum::routing::get;
170pub use axum::routing::patch;
171pub use axum::routing::post;
172pub use axum::routing::put;
173
174#[cfg_attr(mutants, mutants::skip)]
177pub fn ws<H, T>(handler: H) -> axum::routing::MethodRouter
178where
179 T: 'static,
180 H: axum::handler::Handler<T, ()>,
181{
182 axum::routing::get(handler)
183}
184
185#[macro_export]
186macro_rules! routes {
201 ( $($method:ident ( $path:expr => $handler:expr )),* $(,)? ) => {
202 {
203 let mut router = $crate::Router::new();
204 $(
205 router = router.route($path, $crate::routing::$method($handler));
206 )*
207 router
208 }
209 };
210}
211
212#[cfg(test)]
213#[allow(clippy::unwrap_used, clippy::expect_used)]
214mod tests {
215 use super::*;
216 use axum::body::Body;
217 use axum::http::{Request, StatusCode};
218 use tower::ServiceExt;
219
220 async fn mock_handler() -> &'static str {
221 "hello"
222 }
223
224 #[tokio::test]
225 async fn test_router_routes() {
226 let router = Router::new()
227 .route("/", get(mock_handler))
228 .route("/post", post(mock_handler));
229
230 let app = router.into_axum();
231
232 let response = app
233 .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
234 .await
235 .unwrap();
236 assert_eq!(response.status(), StatusCode::OK);
237 }
238
239 #[tokio::test]
240 async fn test_routes_macro() {
241 let router = routes![
242 get("/" => mock_handler),
243 post("/submit" => mock_handler),
244 ];
245
246 let app = router.into_axum();
247
248 let response = app
249 .oneshot(
250 Request::builder()
251 .uri("/submit")
252 .method("POST")
253 .body(Body::empty())
254 .unwrap(),
255 )
256 .await
257 .unwrap();
258 assert_eq!(response.status(), StatusCode::OK);
259 }
260
261 #[tokio::test]
262 async fn test_router_nesting() {
263 let api_router = Router::new().route("/users", get(mock_handler));
264 let router = Router::new().nest("/api", api_router);
265 let app = router.into_axum();
266
267 let response = app
268 .oneshot(
269 Request::builder()
270 .uri("/api/users")
271 .body(Body::empty())
272 .unwrap(),
273 )
274 .await
275 .unwrap();
276 assert_eq!(response.status(), StatusCode::OK);
277 }
278
279 #[tokio::test]
280 async fn test_nest_axum() {
281 let axum_router = AxumRouter::new().route("/raw", axum::routing::get(mock_handler));
282 let router = Router::new().nest_axum("/api", axum_router);
283 let app = router.into_axum();
284
285 let response = app
286 .oneshot(
287 Request::builder()
288 .uri("/api/raw")
289 .body(Body::empty())
290 .unwrap(),
291 )
292 .await
293 .unwrap();
294 assert_eq!(response.status(), StatusCode::OK);
295 }
296
297 #[tokio::test]
298 async fn test_ws_routing() {
299 let router = Router::new().ws("/chat", mock_handler);
300 let app = router.into_axum();
301
302 let response = app
303 .oneshot(Request::builder().uri("/chat").body(Body::empty()).unwrap())
304 .await
305 .unwrap();
306 assert_eq!(response.status(), StatusCode::OK);
307 }
308
309 #[tokio::test]
310 async fn test_merge_axum() {
311 let axum_router = AxumRouter::new().route("/merged", axum::routing::get(mock_handler));
312 let router = Router::new().merge_axum(axum_router);
313 let app = router.into_axum();
314
315 let response = app
316 .oneshot(
317 Request::builder()
318 .uri("/merged")
319 .body(Body::empty())
320 .unwrap(),
321 )
322 .await
323 .unwrap();
324 assert_eq!(response.status(), StatusCode::OK);
325 }
326
327 #[tokio::test]
328 async fn test_router_from_into_conversions() {
329 let raw_axum = AxumRouter::new().route("/from-axum", axum::routing::get(mock_handler));
330 let mut rullst_router: Router = raw_axum.into();
331 let _ = rullst_router.as_axum();
332 let _ = rullst_router.as_axum_mut();
333
334 let converted_back: AxumRouter = rullst_router.into();
335 let response = converted_back
336 .oneshot(
337 Request::builder()
338 .uri("/from-axum")
339 .body(Body::empty())
340 .unwrap(),
341 )
342 .await
343 .unwrap();
344 assert_eq!(response.status(), StatusCode::OK);
345 }
346
347 #[tokio::test]
348 async fn test_router_fallback_and_deref() {
349 async fn custom_404() -> (StatusCode, &'static str) {
350 (StatusCode::NOT_FOUND, "custom not found")
351 }
352
353 let router = Router::new()
354 .route("/ok", get(mock_handler))
355 .fallback(custom_404);
356
357 let app: AxumRouter = router.into();
358
359 let response = app
360 .oneshot(
361 Request::builder()
362 .uri("/nonexistent")
363 .body(Body::empty())
364 .unwrap(),
365 )
366 .await
367 .unwrap();
368 assert_eq!(response.status(), StatusCode::NOT_FOUND);
369 }
370}
371
372#[cfg(test)]
373#[path = "routing_contract_tests.rs"]
374mod contract_tests;