diff --git a/axum-core/src/lib.rs b/axum-core/src/lib.rs
index 6d15aade..63e363e1 100644
--- a/axum-core/src/lib.rs
+++ b/axum-core/src/lib.rs
@@ -46,7 +46,6 @@
#![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(test, allow(clippy::float_cmp))]
#[macro_use]
diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml
index 47b5ca64..e9abc7fe 100644
--- a/axum-extra/Cargo.toml
+++ b/axum-extra/Cargo.toml
@@ -58,4 +58,4 @@ tower = { version = "0.4", features = ["util"] }
[package.metadata.docs.rs]
all-features = true
-rustdocflags = ["--cfg", "docsrs"]
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr b/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr
index 1694a87e..078b2b03 100644
--- a/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr
+++ b/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr
@@ -4,4 +4,14 @@ error[E0277]: the trait bound `bool: FromRequest
` is not satisfied
4 | async fn handler(foo: bool) {}
| ^^^^ the trait `FromRequest` is not implemented for `bool`
|
+ = help: the following other types implement trait `FromRequest`:
+ ()
+ (T1, T2)
+ (T1, T2, T3)
+ (T1, T2, T3, T4)
+ (T1, T2, T3, T4, T5)
+ (T1, T2, T3, T4, T5, T6)
+ (T1, T2, T3, T4, T5, T6, T7)
+ (T1, T2, T3, T4, T5, T6, T7, T8)
+ and 33 others
= help: see issue #48214
diff --git a/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr b/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr
index 37fba61f..c3ca7e1e 100644
--- a/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr
+++ b/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr
@@ -4,6 +4,16 @@ error[E0277]: the trait bound `bool: IntoResponse` is not satisfied
4 | async fn handler() -> bool {
| ^^^^ the trait `IntoResponse` is not implemented for `bool`
|
+ = help: the following other types implement trait `IntoResponse`:
+ &'static [u8]
+ &'static str
+ ()
+ (Response<()>, R)
+ (Response<()>, T1, R)
+ (Response<()>, T1, T2, R)
+ (Response<()>, T1, T2, T3, R)
+ (Response<()>, T1, T2, T3, T4, R)
+ and 123 others
note: required by a bound in `__axum_macros_check_handler_into_response::{closure#0}::check`
--> tests/debug_handler/fail/wrong_return_type.rs:4:23
|
diff --git a/axum-macros/tests/typed_path/fail/not_deserialize.stderr b/axum-macros/tests/typed_path/fail/not_deserialize.stderr
index 3c6f6258..7581b399 100644
--- a/axum-macros/tests/typed_path/fail/not_deserialize.stderr
+++ b/axum-macros/tests/typed_path/fail/not_deserialize.stderr
@@ -4,6 +4,16 @@ error[E0277]: the trait bound `for<'de> MyPath: serde::de::Deserialize<'de>` is
3 | #[derive(TypedPath)]
| ^^^^^^^^^ the trait `for<'de> serde::de::Deserialize<'de>` is not implemented for `MyPath`
|
+ = help: the following other types implement trait `serde::de::Deserialize<'de>`:
+ &'a [u8]
+ &'a serde_json::raw::RawValue
+ &'a std::path::Path
+ &'a str
+ ()
+ (T0, T1)
+ (T0, T1, T2)
+ (T0, T1, T2, T3)
+ and 138 others
= note: required because of the requirements on the impl of `serde::de::DeserializeOwned` for `MyPath`
= note: required because of the requirements on the impl of `FromRequest` for `axum::extract::Path`
= note: this error originates in the derive macro `TypedPath` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md
index 15b30015..54d5ee9a 100644
--- a/axum/CHANGELOG.md
+++ b/axum/CHANGELOG.md
@@ -27,11 +27,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`/foo/`). That is no longer supported and such requests will now be sent to
the fallback. Consider using `axum_extra::routing::RouterExt::route_with_tsr`
if you want the old behavior ([#1119])
+- **added** Implement `TryFrom` for `MethodFilter` and use new `NoMatchingMethodFilter` error in case of failure ([#1130])
+- **added:** Support running extractors from `middleware::from_fn` functions ([#1088])
[#1077]: https://github.com/tokio-rs/axum/pull/1077
[#1086]: https://github.com/tokio-rs/axum/pull/1086
+[#1088]: https://github.com/tokio-rs/axum/pull/1088
[#1102]: https://github.com/tokio-rs/axum/pull/1102
[#1119]: https://github.com/tokio-rs/axum/pull/1119
+[#1130]: https://github.com/tokio-rs/axum/pull/1130
[#924]: https://github.com/tokio-rs/axum/pull/924
# 0.5.10 (28. June, 2022)
diff --git a/axum/README.md b/axum/README.md
index 8fcb9cc1..b78148c3 100644
--- a/axum/README.md
+++ b/axum/README.md
@@ -23,6 +23,12 @@ In particular the last point is what sets `axum` apart from other frameworks.
authorization, and more, for free. It also enables you to share middleware with
applications written using [`hyper`] or [`tonic`].
+## Breaking changes
+
+We are currently working towards axum 0.6 so the `main` branch contains breaking
+changes. See the [`0.5.x`] branch for whats released to crates.io and up to date
+changelogs.
+
## Usage example
```rust
@@ -159,3 +165,4 @@ additional terms or conditions.
[showcases]: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md#project-showcase
[tutorials]: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md#tutorials
[license]: https://github.com/tokio-rs/axum/blob/main/axum/LICENSE
+[`0.5.x`]: https://github.com/tokio-rs/axum/tree/0.5.x
diff --git a/axum/src/docs/extract.md b/axum/src/docs/extract.md
index eedf5ec5..92784a65 100644
--- a/axum/src/docs/extract.md
+++ b/axum/src/docs/extract.md
@@ -12,6 +12,7 @@ Types and traits for extracting data from requests.
- [Defining custom extractors](#defining-custom-extractors)
- [Accessing other extractors in `FromRequest` implementations](#accessing-other-extractors-in-fromrequest-implementations)
- [Request body extractors](#request-body-extractors)
+- [Running extractors from middleware](#running-extractors-from-middleware)
# Intro
@@ -579,6 +580,62 @@ let app = Router::new()
# };
```
+# Running extractors from middleware
+
+Extractors can also be run from middleware by making a [`RequestParts`] and
+running your extractor:
+
+```rust
+use axum::{
+ Router,
+ middleware::{self, Next},
+ extract::{RequestParts, TypedHeader},
+ http::{Request, StatusCode},
+ response::Response,
+ headers::authorization::{Authorization, Bearer},
+};
+
+async fn auth_middleware(
+ request: Request,
+ next: Next,
+) -> Result
+where
+ B: Send,
+{
+ // running extractors requires a `RequestParts`
+ let mut request_parts = RequestParts::new(request);
+
+ // `TypedHeader>` extracts the auth token but
+ // `RequestParts::extract` works with anything that implements `FromRequest`
+ let auth = request_parts.extract::>>()
+ .await
+ .map_err(|_| StatusCode::UNAUTHORIZED)?;
+
+ if !token_is_valid(auth.token()) {
+ return Err(StatusCode::UNAUTHORIZED);
+ }
+
+ // get the request back so we can run `next`
+ //
+ // `try_into_request` will fail if you have extracted the request body. We
+ // know that `TypedHeader` never does that.
+ //
+ // see the `consume-body-in-extractor-or-middleware` example if you need to
+ // extract the body
+ let request = request_parts.try_into_request().expect("body extracted");
+
+ Ok(next.run(request).await)
+}
+
+fn token_is_valid(token: &str) -> bool {
+ // ...
+ # false
+}
+
+let app = Router::new().layer(middleware::from_fn(auth_middleware));
+# let _: Router = app;
+```
+
[`body::Body`]: crate::body::Body
[customize-extractor-error]: https://github.com/tokio-rs/axum/blob/main/examples/customize-extractor-error/src/main.rs
[`HeaderMap`]: https://docs.rs/http/latest/http/header/struct.HeaderMap.html
diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs
index 9f9563b5..8e46fa69 100644
--- a/axum/src/middleware/from_fn.rs
+++ b/axum/src/middleware/from_fn.rs
@@ -3,13 +3,15 @@ use crate::{
response::{IntoResponse, Response},
BoxError,
};
+use axum_core::extract::{FromRequest, RequestParts};
+use futures_util::future::BoxFuture;
use http::Request;
-use pin_project_lite::pin_project;
use std::{
any::type_name,
convert::Infallible,
fmt,
future::Future,
+ marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
@@ -23,8 +25,8 @@ use tower_service::Service;
/// `from_fn` requires the function given to
///
/// 1. Be an `async fn`.
-/// 2. Take [`Request`](http::Request) as the first argument.
-/// 3. Take [`Next`](Next) as the second argument.
+/// 2. Take one or more [extractors] as the first arguments.
+/// 3. Take [`Next`](Next) as the final argument.
/// 4. Return something that implements [`IntoResponse`].
///
/// # Example
@@ -62,6 +64,37 @@ use tower_service::Service;
/// # let app: Router = app;
/// ```
///
+/// # Running extractors
+///
+/// ```rust
+/// use axum::{
+/// Router,
+/// extract::{TypedHeader, Query},
+/// headers::authorization::{Authorization, Bearer},
+/// http::Request,
+/// middleware::{self, Next},
+/// response::Response,
+/// routing::get,
+/// };
+/// use std::collections::HashMap;
+///
+/// async fn my_middleware(
+/// TypedHeader(auth): TypedHeader>,
+/// Query(query_params): Query>,
+/// req: Request,
+/// next: Next,
+/// ) -> Response {
+/// // do something with `auth` and `query_params`...
+///
+/// next.run(req).await
+/// }
+///
+/// let app = Router::new()
+/// .route("/", get(|| async { /* ... */ }))
+/// .route_layer(middleware::from_fn(my_middleware));
+/// # let app: Router = app;
+/// ```
+///
/// # Passing state
///
/// State can be passed to the function like so:
@@ -114,11 +147,10 @@ use tower_service::Service;
/// struct State { /* ... */ }
///
/// async fn my_middleware(
+/// Extension(state): Extension,
/// req: Request,
/// next: Next,
/// ) -> Response {
-/// let state: &State = req.extensions().get().unwrap();
-///
/// // ...
/// # ().into_response()
/// }
@@ -134,8 +166,13 @@ use tower_service::Service;
/// );
/// # let app: Router = app;
/// ```
-pub fn from_fn(f: F) -> FromFnLayer {
- FromFnLayer { f }
+///
+/// [extractors]: crate::extract::FromRequest
+pub fn from_fn(f: F) -> FromFnLayer {
+ FromFnLayer {
+ f,
+ _extractor: PhantomData,
+ }
}
/// A [`tower::Layer`] from an async function.
@@ -143,26 +180,41 @@ pub fn from_fn(f: F) -> FromFnLayer {
/// [`tower::Layer`] is used to apply middleware to [`Router`](crate::Router)'s.
///
/// Created with [`from_fn`]. See that function for more details.
-#[derive(Clone, Copy)]
-pub struct FromFnLayer {
+pub struct FromFnLayer {
f: F,
+ _extractor: PhantomData T>,
}
-impl Layer for FromFnLayer
+impl Clone for FromFnLayer
where
F: Clone,
{
- type Service = FromFn;
+ fn clone(&self) -> Self {
+ Self {
+ f: self.f.clone(),
+ _extractor: self._extractor,
+ }
+ }
+}
+
+impl Copy for FromFnLayer where F: Copy {}
+
+impl Layer for FromFnLayer
+where
+ F: Clone,
+{
+ type Service = FromFn;
fn layer(&self, inner: S) -> Self::Service {
FromFn {
f: self.f.clone(),
inner,
+ _extractor: PhantomData,
}
}
}
-impl fmt::Debug for FromFnLayer {
+impl fmt::Debug for FromFnLayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FromFnLayer")
// Write out the type name, without quoting it as `&type_name::()` would
@@ -174,50 +226,94 @@ impl fmt::Debug for FromFnLayer {
/// A middleware created from an async function.
///
/// Created with [`from_fn`]. See that function for more details.
-#[derive(Clone, Copy)]
-pub struct FromFn {
+pub struct FromFn {
f: F,
inner: S,
+ _extractor: PhantomData T>,
}
-impl Service> for FromFn
+impl Clone for FromFn
where
- F: FnMut(Request, Next) -> Fut,
- Fut: Future