mirror of
https://github.com/tokio-rs/axum.git
synced 2025-01-11 12:31:25 +01:00
Change how Resource
s are added to Router
s (#544)
This commit is contained in:
parent
44a49cb199
commit
ab9f1ef993
2 changed files with 125 additions and 75 deletions
|
@ -1,6 +1,6 @@
|
|||
//! Additional types for defining routes.
|
||||
|
||||
use axum::Router;
|
||||
use axum::{body::Body, Router};
|
||||
|
||||
mod resource;
|
||||
|
||||
|
@ -8,24 +8,55 @@ pub use self::resource::Resource;
|
|||
|
||||
/// Extension trait that adds additional methods to [`Router`].
|
||||
pub trait RouterExt<B>: sealed::Sealed {
|
||||
/// Add a [`Resource`] to the router.
|
||||
/// Add the routes from `T`'s [`HasRoutes::routes`] to this router.
|
||||
///
|
||||
/// See [`Resource`] for more details.
|
||||
fn resource<F>(self, name: &str, f: F) -> Self
|
||||
/// # Example
|
||||
///
|
||||
/// Using [`Resource`] which implements [`HasRoutes`]:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{Router, routing::get};
|
||||
/// use axum_extra::routing::{RouterExt, Resource};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .with(
|
||||
/// Resource::named("users")
|
||||
/// .index(|| async {})
|
||||
/// .create(|| async {})
|
||||
/// )
|
||||
/// .with(
|
||||
/// Resource::named("teams").index(|| async {})
|
||||
/// );
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// ```
|
||||
fn with<T>(self, routes: T) -> Self
|
||||
where
|
||||
F: FnOnce(resource::Resource<B>) -> resource::Resource<B>;
|
||||
T: HasRoutes<B>;
|
||||
}
|
||||
|
||||
impl<B> RouterExt<B> for Router<B> {
|
||||
fn resource<F>(self, name: &str, f: F) -> Self
|
||||
impl<B> RouterExt<B> for Router<B>
|
||||
where
|
||||
B: Send + 'static,
|
||||
{
|
||||
fn with<T>(self, routes: T) -> Self
|
||||
where
|
||||
F: FnOnce(resource::Resource<B>) -> resource::Resource<B>,
|
||||
T: HasRoutes<B>,
|
||||
{
|
||||
f(resource::Resource {
|
||||
name: name.to_owned(),
|
||||
router: self,
|
||||
})
|
||||
.router
|
||||
self.merge(routes.routes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for things that can provide routes.
|
||||
///
|
||||
/// Used with [`RouterExt::with`].
|
||||
pub trait HasRoutes<B = Body> {
|
||||
/// Get the routes.
|
||||
fn routes(self) -> Router<B>;
|
||||
}
|
||||
|
||||
impl<B> HasRoutes<B> for Router<B> {
|
||||
fn routes(self) -> Router<B> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use super::HasRoutes;
|
||||
use axum::{
|
||||
body::{Body, BoxBody},
|
||||
handler::Handler,
|
||||
|
@ -14,35 +15,36 @@ use tower_service::Service;
|
|||
///
|
||||
/// ```rust
|
||||
/// use axum::{Router, routing::get, extract::Path};
|
||||
/// use axum_extra::routing::RouterExt;
|
||||
/// use axum_extra::routing::{RouterExt, Resource};
|
||||
///
|
||||
/// let app = Router::new().resource("users", |r| {
|
||||
/// let users = Resource::named("users")
|
||||
/// // Define a route for `GET /users`
|
||||
/// r.index(|| async {})
|
||||
/// // `POST /users`
|
||||
/// .create(|| async {})
|
||||
/// // `GET /users/new`
|
||||
/// .new(|| async {})
|
||||
/// // `GET /users/:users_id`
|
||||
/// .show(|Path(user_id): Path<u64>| async {})
|
||||
/// // `GET /users/:users_id/edit`
|
||||
/// .edit(|Path(user_id): Path<u64>| async {})
|
||||
/// // `PUT or PATCH /users/:users_id`
|
||||
/// .update(|Path(user_id): Path<u64>| async {})
|
||||
/// // `DELETE /users/:users_id`
|
||||
/// .destroy(|Path(user_id): Path<u64>| async {})
|
||||
/// // Nest another router at the "member level"
|
||||
/// // This defines a route for `GET /users/:users_id/tweets`
|
||||
/// .nest(Router::new().route(
|
||||
/// "/tweets",
|
||||
/// get(|Path(user_id): Path<u64>| async {}),
|
||||
/// ))
|
||||
/// // Nest another router at the "collection level"
|
||||
/// // This defines a route for `GET /users/featured`
|
||||
/// .nest_collection(
|
||||
/// Router::new().route("/featured", get(|| async {})),
|
||||
/// )
|
||||
/// });
|
||||
/// .index(|| async {})
|
||||
/// // `POST /users`
|
||||
/// .create(|| async {})
|
||||
/// // `GET /users/new`
|
||||
/// .new(|| async {})
|
||||
/// // `GET /users/:users_id`
|
||||
/// .show(|Path(user_id): Path<u64>| async {})
|
||||
/// // `GET /users/:users_id/edit`
|
||||
/// .edit(|Path(user_id): Path<u64>| async {})
|
||||
/// // `PUT or PATCH /users/:users_id`
|
||||
/// .update(|Path(user_id): Path<u64>| async {})
|
||||
/// // `DELETE /users/:users_id`
|
||||
/// .destroy(|Path(user_id): Path<u64>| async {})
|
||||
/// // Nest another router at the "member level"
|
||||
/// // This defines a route for `GET /users/:users_id/tweets`
|
||||
/// .nest(Router::new().route(
|
||||
/// "/tweets",
|
||||
/// get(|Path(user_id): Path<u64>| async {}),
|
||||
/// ))
|
||||
/// // Nest another router at the "collection level"
|
||||
/// // This defines a route for `GET /users/featured`
|
||||
/// .nest_collection(
|
||||
/// Router::new().route("/featured", get(|| async {})),
|
||||
/// );
|
||||
///
|
||||
/// let app = Router::new().with(users);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
|
@ -52,27 +54,17 @@ pub struct Resource<B = Body> {
|
|||
}
|
||||
|
||||
impl<B: Send + 'static> Resource<B> {
|
||||
fn index_create_path(&self) -> String {
|
||||
format!("/{}", self.name)
|
||||
/// Create a `Resource` with the given name.
|
||||
///
|
||||
/// All routes will be nested at `/{resource_name}`.
|
||||
pub fn named(resource_name: &str) -> Self {
|
||||
Self {
|
||||
name: resource_name.to_string(),
|
||||
router: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn show_update_destroy_path(&self) -> String {
|
||||
format!("/{0}/:{0}_id", self.name)
|
||||
}
|
||||
|
||||
fn route<T>(mut self, path: &str, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.router = self.router.route(path, svc);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a handler at `GET /resource_name`.
|
||||
/// Add a handler at `GET /{resource_name}`.
|
||||
pub fn index<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
|
@ -173,6 +165,32 @@ impl<B: Send + 'static> Resource<B> {
|
|||
self.router = self.router.nest(&path, svc);
|
||||
self
|
||||
}
|
||||
|
||||
fn index_create_path(&self) -> String {
|
||||
format!("/{}", self.name)
|
||||
}
|
||||
|
||||
fn show_update_destroy_path(&self) -> String {
|
||||
format!("/{0}/:{0}_id", self.name)
|
||||
}
|
||||
|
||||
fn route<T>(mut self, path: &str, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Response = Response<BoxBody>, Error = Infallible>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.router = self.router.route(path, svc);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> HasRoutes<B> for Resource<B> {
|
||||
fn routes(self) -> Router<B> {
|
||||
self.router
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -185,22 +203,23 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn works() {
|
||||
let mut app = Router::new().resource("users", |r| {
|
||||
r.index(|| async { "users#index" })
|
||||
.create(|| async { "users#create" })
|
||||
.new(|| async { "users#new" })
|
||||
.show(|Path(id): Path<u64>| async move { format!("users#show id={}", id) })
|
||||
.edit(|Path(id): Path<u64>| async move { format!("users#edit id={}", id) })
|
||||
.update(|Path(id): Path<u64>| async move { format!("users#update id={}", id) })
|
||||
.destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={}", id) })
|
||||
.nest(Router::new().route(
|
||||
"/tweets",
|
||||
get(|Path(id): Path<u64>| async move { format!("users#tweets id={}", id) }),
|
||||
))
|
||||
.nest_collection(
|
||||
Router::new().route("/featured", get(|| async move { "users#featured" })),
|
||||
)
|
||||
});
|
||||
let users = Resource::named("users")
|
||||
.index(|| async { "users#index" })
|
||||
.create(|| async { "users#create" })
|
||||
.new(|| async { "users#new" })
|
||||
.show(|Path(id): Path<u64>| async move { format!("users#show id={}", id) })
|
||||
.edit(|Path(id): Path<u64>| async move { format!("users#edit id={}", id) })
|
||||
.update(|Path(id): Path<u64>| async move { format!("users#update id={}", id) })
|
||||
.destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={}", id) })
|
||||
.nest(Router::new().route(
|
||||
"/tweets",
|
||||
get(|Path(id): Path<u64>| async move { format!("users#tweets id={}", id) }),
|
||||
))
|
||||
.nest_collection(
|
||||
Router::new().route("/featured", get(|| async move { "users#featured" })),
|
||||
);
|
||||
|
||||
let mut app = Router::new().with(users);
|
||||
|
||||
assert_eq!(
|
||||
call_route(&mut app, Method::GET, "/users").await,
|
||||
|
|
Loading…
Reference in a new issue