Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace HasRoutes with Into<Router> #819

Merged
merged 3 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions axum-extra/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning].
- **breaking:** `CachedRejection` has been removed ([#699])
- **breaking:** `<Cached<T> as FromRequest>::Rejection` is now `T::Rejection`. ([#699])
- **breaking:** `middleware::from_fn` has been moved into the main axum crate ([#719])
- **breaking:** `HasRoutes` has been moved into axum
- **breaking:** `RouterExt::with` method has been removed. Use `Router::merge` instead. It works
identically

[#666]: https://github.com/tokio-rs/axum/pull/666
[#699]: https://github.com/tokio-rs/axum/pull/699
Expand Down
48 changes: 1 addition & 47 deletions axum-extra/src/routing/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Additional types for defining routes.

use axum::{body::Body, handler::Handler, Router};
use axum::{handler::Handler, Router};

mod resource;

Expand All @@ -17,31 +17,6 @@ pub use self::typed::{FirstElementIs, TypedPath};

/// Extension trait that adds additional methods to [`Router`].
pub trait RouterExt<B>: sealed::Sealed {
/// Add the routes from `T`'s [`HasRoutes::routes`] to this router.
///
/// # 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
T: HasRoutes<B>;

/// Add a typed `GET` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
Expand Down Expand Up @@ -151,13 +126,6 @@ impl<B> RouterExt<B> for Router<B>
where
B: axum::body::HttpBody + Send + 'static,
{
fn with<T>(self, routes: T) -> Self
where
T: HasRoutes<B>,
{
self.merge(routes.routes())
}

#[cfg(feature = "typed-routing")]
fn typed_get<H, T, P>(self, handler: H) -> Self
where
Expand Down Expand Up @@ -239,20 +207,6 @@ where
}
}

/// 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
}
}

mod sealed {
pub trait Sealed {}
impl<B> Sealed for axum::Router<B> {}
Expand Down
6 changes: 2 additions & 4 deletions axum-extra/src/routing/resource.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use super::HasRoutes;
use axum::{
body::Body,
handler::Handler,
http::Request,
response::Response,
routing::{delete, get, on, post, MethodFilter},
routing::{delete, get, on, post, HasRoutes, MethodFilter},
Router,
};
use std::convert::Infallible;
Expand Down Expand Up @@ -192,7 +191,6 @@ impl<B> HasRoutes<B> for Resource<B> {
mod tests {
#[allow(unused_imports)]
use super::*;
use crate::routing::RouterExt;
use axum::{extract::Path, http::Method, Router};
use tower::ServiceExt;

Expand All @@ -214,7 +212,7 @@ mod tests {
Router::new().route("/featured", get(|| async move { "users#featured" })),
);

let mut app = Router::new().with(users);
let mut app = Router::new().merge(users);

assert_eq!(
call_route(&mut app, Method::GET, "/users").await,
Expand Down
4 changes: 4 additions & 0 deletions axum/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **added:** `Extension<_>` can now be used in tuples for building responses, and will set an
extension on the response ([#797])
- **added:** Implement `tower::Layer` for `Extension` ([#801])
- **added:** Add `HasRoutes` trait allowing users to define their own types that work with
`Router::merge`
- **changed:** `Router::merge` now requires the argument to implement `HasRoutes`. This is
implemented for `Router`
- **breaking:** `sse::Event` now accepts types implementing `AsRef<str>` instead of `Into<String>`
as field values.
- **breaking:** `sse::Event` now panics if a setter method is called twice instead of silently
Expand Down
77 changes: 75 additions & 2 deletions axum/src/routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,16 @@ where
}

#[doc = include_str!("../docs/routing/merge.md")]
pub fn merge(mut self, other: Router<B>) -> Self {
pub fn merge<R>(mut self, other: R) -> Self
where
R: HasRoutes<B>,
{
let Router {
routes,
node,
fallback,
nested_at_root,
} = other;
} = other.routes();

for (id, route) in routes {
let path = node
Expand Down Expand Up @@ -697,3 +700,73 @@ fn traits() {
use crate::test_helpers::*;
assert_send::<Router<()>>();
}

/// Trait for things that can provide routes.
///
/// Used with [`Router::merge`].
///
/// # Example
///
/// This can be used to build your own abstractions for building routes and have it work with
/// [`Router::merge`].
///
/// Consider this hypothetical API for having a bunch of paths all go to the same handler:
///
/// ```rust
/// use axum::{
/// Router,
/// routing::{get, post, MethodRouter, HasRoutes},
/// handler::Handler,
/// body::HttpBody,
/// };
///
/// struct ToSameHandler<B> {
/// route: MethodRouter<B>,
/// routes: Router<B>,
/// }
///
/// impl<B> ToSameHandler<B>
/// where
/// B: HttpBody + Send + 'static,
/// {
/// fn new<H, T>(handler: H) -> Self
/// where
/// H: Handler<T, B>,
/// T: 'static,
/// {
/// Self {
/// route: get(handler),
/// routes: Router::new(),
/// }
/// }
///
/// fn add_path(mut self, path: &str) -> Self {
/// self.routes = self.routes.route(path, self.route.clone());
/// self
/// }
/// }
///
/// impl<B> HasRoutes<B> for ToSameHandler<B> {
/// fn routes(self) -> Router<B> {
/// self.routes
/// }
/// }
///
/// let app = Router::new().merge(
/// ToSameHandler::new(|| async {})
/// .add_path("/foo")
/// .add_path("/bar")
/// .add_path("/baz")
/// );
/// # let _: Router<axum::body::Body> = app;
/// ```
pub trait HasRoutes<B> {
davidpdrsn marked this conversation as resolved.
Show resolved Hide resolved
/// Get the routes.
fn routes(self) -> Router<B>;
}

impl<B> HasRoutes<B> for Router<B> {
fn routes(self) -> Router<B> {
self
}
}