Skip to main content

oxide_framework_core/
router.rs

1use axum::{
2    Router,
3    handler::Handler,
4    routing::{delete, get, head, options, patch, post, put},
5};
6
7/// HTTP methods supported by the framework.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum Method {
10    GET,
11    POST,
12    PUT,
13    DELETE,
14    PATCH,
15    HEAD,
16    OPTIONS,
17}
18
19/// Thin wrapper around `axum::Router` that provides a simplified registration API
20/// with support for grouping, nesting, and merging.
21pub struct OxideRouter<S = ()> {
22    inner: Router<S>,
23}
24
25impl<S: Clone + Send + Sync + 'static> OxideRouter<S> {
26    pub fn new() -> Self {
27        Self {
28            inner: Router::new(),
29        }
30    }
31
32    /// Wrap an existing `axum::Router` in an `OxideRouter`.
33    pub fn from_router(router: Router<S>) -> Self {
34        Self { inner: router }
35    }
36
37    /// Nest this router under a prefix, consuming and returning `self`.
38    /// Handles empty and "/" prefixes natively via merge (Axum 0.8 compatibility).
39    pub fn nest_self(self, prefix: &str) -> Self {
40        if prefix.is_empty() || prefix == "/" {
41            self
42        } else {
43            Self {
44                inner: Router::new().nest(prefix, self.inner),
45            }
46        }
47    }
48
49    /// Register a handler for the given method and path.
50    pub fn route<H, T>(mut self, method: Method, path: &str, handler: H) -> Self
51    where
52        H: Handler<T, S>,
53        T: 'static,
54    {
55        let method_router = match method {
56            Method::GET => get(handler),
57            Method::POST => post(handler),
58            Method::PUT => put(handler),
59            Method::DELETE => delete(handler),
60            Method::PATCH => patch(handler),
61            Method::HEAD => head(handler),
62            Method::OPTIONS => options(handler),
63        };
64        self.inner = self.inner.route(path, method_router);
65        self
66    }
67
68    // -- Convenience methods --------------------------------------------------
69
70    pub fn get<H, T>(self, path: &str, handler: H) -> Self
71    where
72        H: Handler<T, S>,
73        T: 'static,
74    {
75        self.route(Method::GET, path, handler)
76    }
77
78    pub fn post<H, T>(self, path: &str, handler: H) -> Self
79    where
80        H: Handler<T, S>,
81        T: 'static,
82    {
83        self.route(Method::POST, path, handler)
84    }
85
86    pub fn put<H, T>(self, path: &str, handler: H) -> Self
87    where
88        H: Handler<T, S>,
89        T: 'static,
90    {
91        self.route(Method::PUT, path, handler)
92    }
93
94    pub fn delete<H, T>(self, path: &str, handler: H) -> Self
95    where
96        H: Handler<T, S>,
97        T: 'static,
98    {
99        self.route(Method::DELETE, path, handler)
100    }
101
102    pub fn patch<H, T>(self, path: &str, handler: H) -> Self
103    where
104        H: Handler<T, S>,
105        T: 'static,
106    {
107        self.route(Method::PATCH, path, handler)
108    }
109
110    // -- Composition ----------------------------------------------------------
111
112    /// Merge another `OxideRouter` into this one (flat merge, no prefix).
113    pub fn merge(mut self, other: OxideRouter<S>) -> Self {
114        self.inner = self.inner.merge(other.inner);
115        self
116    }
117
118    /// Nest a sub-router under the given prefix. Handles empty and "/" prefixes gracefully.
119    ///
120    /// ```rust,ignore
121    /// let api = OxideRouter::new()
122    ///     .get("/users", list_users)
123    ///     .post("/users", create_user);
124    ///
125    /// let app_router = OxideRouter::new()
126    ///     .nest("/api", api);
127    /// // produces: GET /api/users, POST /api/users
128    /// ```
129    pub fn nest(mut self, prefix: &str, other: OxideRouter<S>) -> Self {
130        if prefix.is_empty() || prefix == "/" {
131            self.inner = self.inner.merge(other.inner);
132        } else {
133            self.inner = self.inner.nest(prefix, other.inner);
134        }
135        self
136    }
137
138    /// Consume the wrapper and return the underlying `axum::Router`.
139    pub fn into_inner(self) -> Router<S> {
140        self.inner
141    }
142}
143