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

Type safe routing #756

Merged
merged 28 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b42ccc8
wip
davidpdrsn Feb 10, 2022
3682586
wip
davidpdrsn Feb 11, 2022
c84d6d6
make macro implement trait
davidpdrsn Feb 13, 2022
47f5954
checkpoint
davidpdrsn Feb 13, 2022
4f08719
checkpoint
davidpdrsn Feb 13, 2022
9f5dbfd
Simplify things quite a bit
davidpdrsn Feb 13, 2022
1b7326e
re-export `axum_macros::TypedPath` from `axum_extra`
davidpdrsn Feb 13, 2022
46f85fd
docs
davidpdrsn Feb 13, 2022
f9e9ce6
add missing feature
davidpdrsn Feb 13, 2022
ca268f6
fix docs link
davidpdrsn Feb 13, 2022
538a8f3
fix features
davidpdrsn Feb 13, 2022
967297b
fix missing imports
davidpdrsn Feb 13, 2022
da7418b
make serde an optional dep again
davidpdrsn Feb 14, 2022
0743a86
ui tests
davidpdrsn Feb 14, 2022
3ddefb4
Break things up a bit
davidpdrsn Feb 14, 2022
58ba5a2
Update span for `FromRequest` impls to point to callsite
davidpdrsn Feb 14, 2022
8ddf495
make docs feature labels show up automatically
davidpdrsn Feb 14, 2022
fcf8796
Apply suggestions from code review
davidpdrsn Feb 14, 2022
feb86f6
add note about Display/Serialize being compatible
davidpdrsn Feb 14, 2022
39406ea
Update axum-extra/src/routing/typed.rs
davidpdrsn Feb 14, 2022
d3bcf77
fix missing docs link
davidpdrsn Feb 14, 2022
cc1f989
what about typed methods?
davidpdrsn Feb 15, 2022
cc2e1be
Revert "what about typed methods?"
davidpdrsn Feb 18, 2022
b2bd51d
don't allow wildcards for now
davidpdrsn Feb 18, 2022
88ef921
percent encode params
davidpdrsn Feb 18, 2022
4819d00
Update axum-extra/src/routing/typed.rs
davidpdrsn Feb 18, 2022
d92e763
rephrase args
davidpdrsn Feb 18, 2022
bba6378
changelog
davidpdrsn Feb 18, 2022
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
9 changes: 7 additions & 2 deletions axum-extra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ repository = "https://github.com/tokio-rs/axum"
version = "0.1.2"

[features]
erased-json = ["serde", "serde_json"]
default = []
davidpdrsn marked this conversation as resolved.
Show resolved Hide resolved
erased-json = ["serde_json", "serde"]
typed-routing = ["axum-macros", "serde", "percent-encoding"]

[dependencies]
axum = { path = "../axum", version = "0.4" }
Expand All @@ -25,11 +27,14 @@ tower-layer = "0.3"
tower-service = "0.3"

# optional dependencies
serde = { version = "1.0.130", optional = true }
axum-macros = { path = "../axum-macros", version = "0.1", optional = true }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0.71", optional = true }
percent-encoding = { version = "2.1", optional = true }

[dev-dependencies]
hyper = "0.14"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.14", features = ["full"] }
tower = { version = "0.4", features = ["util"] }

Expand Down
17 changes: 16 additions & 1 deletion axum-extra/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,24 @@
#![deny(unreachable_pub, private_in_public)]
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(test, allow(clippy::float_cmp))]

pub mod extract;
pub mod response;
pub mod routing;

#[cfg(feature = "typed-routing")]
#[doc(hidden)]
pub mod __private {
//! _not_ public API

use percent_encoding::{AsciiSet, CONTROLS};

pub use percent_encoding::utf8_percent_encode;

// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
pub const PATH_SEGMENT: &AsciiSet = &PATH.add(b'/').add(b'%');
}
195 changes: 194 additions & 1 deletion axum-extra/src/routing/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
//! Additional types for defining routes.

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

mod resource;

#[cfg(feature = "typed-routing")]
mod typed;

pub use self::resource::Resource;

#[cfg(feature = "typed-routing")]
pub use axum_macros::TypedPath;

#[cfg(feature = "typed-routing")]
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.
Expand All @@ -32,6 +41,110 @@ pub trait RouterExt<B>: sealed::Sealed {
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
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_get<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `DELETE` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_delete<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `HEAD` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_head<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `OPTIONS` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_options<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `PATCH` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_patch<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `POST` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_post<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `PUT` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_put<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;

/// Add a typed `TRACE` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_trace<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath;
}

impl<B> RouterExt<B> for Router<B>
Expand All @@ -44,6 +157,86 @@ where
{
self.merge(routes.routes())
}

#[cfg(feature = "typed-routing")]
fn typed_get<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::get(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_delete<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::delete(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_head<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::head(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_options<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::options(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_patch<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::patch(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_post<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::post(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_put<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::put(handler))
}

#[cfg(feature = "typed-routing")]
fn typed_trace<H, T, P>(self, handler: H) -> Self
where
H: Handler<T, B>,
T: FirstElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::trace(handler))
}
}

/// Trait for things that can provide routes.
Expand Down
Loading