diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a983ca8665..62e6247e44 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -117,7 +117,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: # same as `axum-macros/rust-toolchain` - toolchain: nightly-2022-11-18 + toolchain: nightly-2023-04-06 override: true profile: minimal - uses: Swatinem/rust-cache@v1 diff --git a/axum-core/Cargo.toml b/axum-core/Cargo.toml index 129d212ce1..e0055e7a4d 100644 --- a/axum-core/Cargo.toml +++ b/axum-core/Cargo.toml @@ -33,7 +33,8 @@ tower-http = { version = "0.4", optional = true, features = ["limit"] } rustversion = "1.0.9" [dev-dependencies] -axum = { path = "../axum", version = "0.6.0", features = ["headers"] } +axum = { path = "../axum", version = "0.6.0" } +axum-extra = { path = "../axum-extra", features = ["typed-header"] } futures-util = { version = "0.3", default-features = false, features = ["alloc"] } hyper = "0.14.24" tokio = { version = "1.25.0", features = ["macros"] } diff --git a/axum-core/src/ext_traits/request.rs b/axum-core/src/ext_traits/request.rs index df41a3c943..a891922e47 100644 --- a/axum-core/src/ext_traits/request.rs +++ b/axum-core/src/ext_traits/request.rs @@ -139,15 +139,19 @@ pub trait RequestExt: sealed::Sealed + Sized { /// ``` /// use axum::{ /// async_trait, - /// extract::{Request, FromRequest}, - /// headers::{authorization::Bearer, Authorization}, + /// extract::{Path, Request, FromRequest}, /// response::{IntoResponse, Response}, /// body::Body, - /// Json, RequestExt, TypedHeader, + /// Json, RequestExt, + /// }; + /// use axum_extra::{ + /// TypedHeader, + /// headers::{authorization::Bearer, Authorization}, /// }; + /// use std::collections::HashMap; /// /// struct MyExtractor { - /// bearer_token: String, + /// path_params: HashMap, /// payload: T, /// } /// @@ -161,9 +165,10 @@ pub trait RequestExt: sealed::Sealed + Sized { /// type Rejection = Response; /// /// async fn from_request(mut req: Request, _state: &S) -> Result { - /// let TypedHeader(auth_header) = req - /// .extract_parts::>>() + /// let path_params = req + /// .extract_parts::>() /// .await + /// .map(|Path(path_params)| path_params) /// .map_err(|err| err.into_response())?; /// /// let Json(payload) = req @@ -171,10 +176,7 @@ pub trait RequestExt: sealed::Sealed + Sized { /// .await /// .map_err(|err| err.into_response())?; /// - /// Ok(Self { - /// bearer_token: auth_header.token().to_owned(), - /// payload, - /// }) + /// Ok(Self { path_params, payload }) /// } /// } /// ``` diff --git a/axum-core/src/ext_traits/request_parts.rs b/axum-core/src/ext_traits/request_parts.rs index 07a7dbff30..e7063f4d8b 100644 --- a/axum-core/src/ext_traits/request_parts.rs +++ b/axum-core/src/ext_traits/request_parts.rs @@ -17,9 +17,8 @@ pub trait RequestPartsExt: sealed::Sealed + Sized { /// /// ``` /// use axum::{ - /// extract::{Query, TypedHeader, FromRequestParts}, + /// extract::{Query, Path, FromRequestParts}, /// response::{Response, IntoResponse}, - /// headers::UserAgent, /// http::request::Parts, /// RequestPartsExt, /// async_trait, @@ -27,7 +26,7 @@ pub trait RequestPartsExt: sealed::Sealed + Sized { /// use std::collections::HashMap; /// /// struct MyExtractor { - /// user_agent: String, + /// path_params: HashMap, /// query_params: HashMap, /// } /// @@ -39,10 +38,10 @@ pub trait RequestPartsExt: sealed::Sealed + Sized { /// type Rejection = Response; /// /// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - /// let user_agent = parts - /// .extract::>() + /// let path_params = parts + /// .extract::>>() /// .await - /// .map(|user_agent| user_agent.as_str().to_owned()) + /// .map(|Path(path_params)| path_params) /// .map_err(|err| err.into_response())?; /// /// let query_params = parts @@ -51,7 +50,7 @@ pub trait RequestPartsExt: sealed::Sealed + Sized { /// .map(|Query(params)| params) /// .map_err(|err| err.into_response())?; /// - /// Ok(MyExtractor { user_agent, query_params }) + /// Ok(MyExtractor { path_params, query_params }) /// } /// } /// ``` diff --git a/axum-extra/CHANGELOG.md b/axum-extra/CHANGELOG.md index 67ab58f18a..df39eb2c8e 100644 --- a/axum-extra/CHANGELOG.md +++ b/axum-extra/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning]. # Unreleased -- None. +- **added:** Added `TypedHeader` which used to be in `axum` ([#1850]) + +[#1850]: https://github.com/tokio-rs/axum/pull/1850 # 0.7.2 (22. March, 2023) diff --git a/axum-extra/Cargo.toml b/axum-extra/Cargo.toml index 984ebb3f26..20de1aa866 100644 --- a/axum-extra/Cargo.toml +++ b/axum-extra/Cargo.toml @@ -32,6 +32,7 @@ json-lines = [ multipart = ["dep:multer"] protobuf = ["dep:prost"] query = ["dep:serde", "dep:serde_html_form"] +typed-header = ["dep:headers"] typed-routing = ["dep:axum-macros", "dep:serde", "dep:percent-encoding", "dep:serde_html_form", "dep:form_urlencoded"] [dependencies] @@ -52,6 +53,7 @@ tower-service = "0.3" axum-macros = { path = "../axum-macros", version = "0.3.7", optional = true } cookie = { package = "cookie", version = "0.17", features = ["percent-encode"], optional = true } form_urlencoded = { version = "1.1.0", optional = true } +headers = { version = "0.3.8", optional = true } multer = { version = "2.0.0", optional = true } percent-encoding = { version = "2.1", optional = true } prost = { version = "0.11", optional = true } @@ -62,7 +64,8 @@ tokio-stream = { version = "0.1.9", optional = true } tokio-util = { version = "0.7", optional = true } [dev-dependencies] -axum = { path = "../axum", version = "0.6.0", features = ["headers"] } +axum = { path = "../axum", version = "0.6.0" } +futures = "0.3" http-body = "0.4.4" hyper = "0.14" reqwest = { version = "0.11", default-features = false, features = ["json", "stream", "multipart"] } @@ -80,14 +83,15 @@ rustdoc-args = ["--cfg", "docsrs"] allowed = [ "axum", "axum_core", + "axum_macros", "bytes", "cookie", "futures_core", "futures_util", + "headers", + "headers_core", "http", "http_body", - "hyper", - "percent_encoding", "prost", "serde", "tokio", diff --git a/axum-extra/src/extract/cookie/mod.rs b/axum-extra/src/extract/cookie/mod.rs index 5cd021fa70..e0b84c7cbb 100644 --- a/axum-extra/src/extract/cookie/mod.rs +++ b/axum-extra/src/extract/cookie/mod.rs @@ -41,12 +41,14 @@ pub use cookie::Key; /// use axum::{ /// Router, /// routing::{post, get}, -/// extract::TypedHeader, /// response::{IntoResponse, Redirect}, -/// headers::authorization::{Authorization, Bearer}, /// http::StatusCode, /// }; -/// use axum_extra::extract::cookie::{CookieJar, Cookie}; +/// use axum_extra::{ +/// TypedHeader, +/// headers::authorization::{Authorization, Bearer}, +/// extract::cookie::{CookieJar, Cookie}, +/// }; /// /// async fn create_session( /// TypedHeader(auth): TypedHeader>, diff --git a/axum-extra/src/extract/cookie/private.rs b/axum-extra/src/extract/cookie/private.rs index c1b55dbdcd..381735f166 100644 --- a/axum-extra/src/extract/cookie/private.rs +++ b/axum-extra/src/extract/cookie/private.rs @@ -23,12 +23,15 @@ use std::{convert::Infallible, fmt, marker::PhantomData}; /// use axum::{ /// Router, /// routing::{post, get}, -/// extract::{TypedHeader, FromRef}, +/// extract::FromRef, /// response::{IntoResponse, Redirect}, -/// headers::authorization::{Authorization, Bearer}, /// http::StatusCode, /// }; -/// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie, Key}; +/// use axum_extra::{ +/// TypedHeader, +/// headers::authorization::{Authorization, Bearer}, +/// extract::cookie::{PrivateCookieJar, Cookie, Key}, +/// }; /// /// async fn set_secret( /// jar: PrivateCookieJar, diff --git a/axum-extra/src/extract/cookie/signed.rs b/axum-extra/src/extract/cookie/signed.rs index 911082f155..6025d37cba 100644 --- a/axum-extra/src/extract/cookie/signed.rs +++ b/axum-extra/src/extract/cookie/signed.rs @@ -24,12 +24,15 @@ use std::{convert::Infallible, fmt, marker::PhantomData}; /// use axum::{ /// Router, /// routing::{post, get}, -/// extract::{TypedHeader, FromRef}, +/// extract::FromRef, /// response::{IntoResponse, Redirect}, -/// headers::authorization::{Authorization, Bearer}, /// http::StatusCode, /// }; -/// use axum_extra::extract::cookie::{SignedCookieJar, Cookie, Key}; +/// use axum_extra::{ +/// TypedHeader, +/// headers::authorization::{Authorization, Bearer}, +/// extract::cookie::{SignedCookieJar, Cookie, Key}, +/// }; /// /// async fn create_session( /// TypedHeader(auth): TypedHeader>, diff --git a/axum-extra/src/extract/mod.rs b/axum-extra/src/extract/mod.rs index 3ca7749e03..b868acaab3 100644 --- a/axum-extra/src/extract/mod.rs +++ b/axum-extra/src/extract/mod.rs @@ -40,4 +40,8 @@ pub use self::multipart::Multipart; #[doc(no_inline)] pub use crate::json_lines::JsonLines; +#[cfg(feature = "typed-header")] +#[doc(no_inline)] +pub use crate::typed_header::TypedHeader; + pub use self::with_rejection::WithRejection; diff --git a/axum-extra/src/lib.rs b/axum-extra/src/lib.rs index 5d0992549e..732868114d 100644 --- a/axum-extra/src/lib.rs +++ b/axum-extra/src/lib.rs @@ -21,6 +21,7 @@ //! `protobuf` | Enables the `Protobuf` extractor and response | No //! `query` | Enables the `Query` extractor | No //! `typed-routing` | Enables the `TypedPath` routing utilities | No +//! `typed-header` | Enables the `TypedHeader` extractor and response | No //! //! [`axum`]: https://crates.io/crates/axum @@ -79,6 +80,17 @@ pub mod routing; #[cfg(feature = "json-lines")] pub mod json_lines; +#[cfg(feature = "typed-header")] +pub mod typed_header; + +#[cfg(feature = "typed-header")] +#[doc(no_inline)] +pub use headers; + +#[cfg(feature = "typed-header")] +#[doc(inline)] +pub use typed_header::TypedHeader; + #[cfg(feature = "protobuf")] pub mod protobuf; diff --git a/axum-extra/src/response/mod.rs b/axum-extra/src/response/mod.rs index 7926b8c815..6e438e8f11 100644 --- a/axum-extra/src/response/mod.rs +++ b/axum-extra/src/response/mod.rs @@ -9,3 +9,7 @@ pub use erased_json::ErasedJson; #[cfg(feature = "json-lines")] #[doc(no_inline)] pub use crate::json_lines::JsonLines; + +#[cfg(feature = "typed-header")] +#[doc(no_inline)] +pub use crate::typed_header::TypedHeader; diff --git a/axum/src/typed_header.rs b/axum-extra/src/typed_header.rs similarity index 89% rename from axum/src/typed_header.rs rename to axum-extra/src/typed_header.rs index 8cc3720c48..282ff8d599 100644 --- a/axum/src/typed_header.rs +++ b/axum-extra/src/typed_header.rs @@ -1,7 +1,11 @@ -use crate::extract::FromRequestParts; -use async_trait::async_trait; -use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts}; -use headers::HeaderMapExt; +//! Extractor and response for typed headers. + +use axum::{ + async_trait, + extract::FromRequestParts, + response::{IntoResponse, IntoResponseParts, Response, ResponseParts}, +}; +use headers::{Header, HeaderMapExt}; use http::request::Parts; use std::{convert::Infallible, ops::Deref}; @@ -14,11 +18,11 @@ use std::{convert::Infallible, ops::Deref}; /// /// ```rust,no_run /// use axum::{ -/// TypedHeader, -/// headers::UserAgent, /// routing::get, /// Router, /// }; +/// use headers::UserAgent; +/// use axum_extra::TypedHeader; /// /// async fn users_teams_show( /// TypedHeader(user_agent): TypedHeader, @@ -34,10 +38,10 @@ use std::{convert::Infallible, ops::Deref}; /// /// ```rust /// use axum::{ -/// TypedHeader, /// response::IntoResponse, -/// headers::ContentType, /// }; +/// use headers::ContentType; +/// use axum_extra::TypedHeader; /// /// async fn handler() -> (TypedHeader, &'static str) { /// ( @@ -46,7 +50,7 @@ use std::{convert::Infallible, ops::Deref}; /// ) /// } /// ``` -#[cfg(feature = "headers")] +#[cfg(feature = "typed-header")] #[derive(Debug, Clone, Copy)] #[must_use] pub struct TypedHeader(pub T); @@ -54,7 +58,7 @@ pub struct TypedHeader(pub T); #[async_trait] impl FromRequestParts for TypedHeader where - T: headers::Header, + T: Header, S: Send + Sync, { type Rejection = TypedHeaderRejection; @@ -86,7 +90,7 @@ impl Deref for TypedHeader { impl IntoResponseParts for TypedHeader where - T: headers::Header, + T: Header, { type Error = Infallible; @@ -98,7 +102,7 @@ where impl IntoResponse for TypedHeader where - T: headers::Header, + T: Header, { fn into_response(self) -> Response { let mut res = ().into_response(); @@ -107,8 +111,8 @@ where } } -/// Rejection used for [`TypedHeader`](super::TypedHeader). -#[cfg(feature = "headers")] +/// Rejection used for [`TypedHeader`](TypedHeader). +#[cfg(feature = "typed-header")] #[derive(Debug)] pub struct TypedHeaderRejection { name: &'static http::header::HeaderName, @@ -128,7 +132,7 @@ impl TypedHeaderRejection { } /// Additional information regarding a [`TypedHeaderRejection`] -#[cfg(feature = "headers")] +#[cfg(feature = "typed-header")] #[derive(Debug)] #[non_exhaustive] pub enum TypedHeaderRejectionReason { @@ -169,9 +173,10 @@ impl std::error::Error for TypedHeaderRejection { #[cfg(test)] mod tests { use super::*; - use crate::{response::IntoResponse, routing::get, test_helpers::*, Router}; + use crate::test_helpers::*; + use axum::{response::IntoResponse, routing::get, Router}; - #[crate::test] + #[tokio::test] async fn typed_header() { async fn handle( TypedHeader(user_agent): TypedHeader, diff --git a/axum-macros/Cargo.toml b/axum-macros/Cargo.toml index 5599610b97..a087aff4de 100644 --- a/axum-macros/Cargo.toml +++ b/axum-macros/Cargo.toml @@ -30,8 +30,8 @@ syn = { version = "2.0", features = [ ] } [dev-dependencies] -axum = { path = "../axum", version = "0.6.0", features = ["headers", "macros"] } -axum-extra = { path = "../axum-extra", version = "0.7.0", features = ["typed-routing", "cookie-private"] } +axum = { path = "../axum", version = "0.6.0", features = ["macros"] } +axum-extra = { path = "../axum-extra", version = "0.7.0", features = ["typed-routing", "cookie-private", "typed-header"] } rustversion = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/axum-macros/rust-toolchain b/axum-macros/rust-toolchain index 8141c9d1e3..a1eb1fc7a7 100644 --- a/axum-macros/rust-toolchain +++ b/axum-macros/rust-toolchain @@ -1 +1 @@ -nightly-2022-11-18 +nightly-2023-04-06 diff --git a/axum-macros/src/lib.rs b/axum-macros/src/lib.rs index db10c9018e..fa5d26498f 100644 --- a/axum-macros/src/lib.rs +++ b/axum-macros/src/lib.rs @@ -71,10 +71,13 @@ use from_request::Trait::{FromRequest, FromRequestParts}; /// ``` /// use axum_macros::FromRequest; /// use axum::{ -/// extract::{Extension, TypedHeader}, -/// headers::ContentType, +/// extract::Extension, /// body::Bytes, /// }; +/// use axum_extra::{ +/// TypedHeader, +/// headers::ContentType, +/// }; /// /// #[derive(FromRequest)] /// struct MyExtractor { @@ -116,10 +119,13 @@ use from_request::Trait::{FromRequest, FromRequestParts}; /// ``` /// use axum_macros::FromRequest; /// use axum::{ -/// extract::{Extension, TypedHeader}, -/// headers::ContentType, +/// extract::Extension, /// body::Bytes, /// }; +/// use axum_extra::{ +/// TypedHeader, +/// headers::ContentType, +/// }; /// /// #[derive(FromRequest)] /// struct MyExtractor { @@ -158,9 +164,10 @@ use from_request::Trait::{FromRequest, FromRequestParts}; /// /// ``` /// use axum_macros::FromRequest; -/// use axum::{ -/// extract::{TypedHeader, rejection::TypedHeaderRejection}, +/// use axum_extra::{ +/// TypedHeader, /// headers::{ContentType, UserAgent}, +/// typed_header::TypedHeaderRejection, /// }; /// /// #[derive(FromRequest)] @@ -368,7 +375,10 @@ pub fn derive_from_request(item: TokenStream) -> TokenStream { /// ``` /// use axum_macros::FromRequestParts; /// use axum::{ -/// extract::{Query, TypedHeader}, +/// extract::Query, +/// }; +/// use axum_extra::{ +/// TypedHeader, /// headers::ContentType, /// }; /// use std::collections::HashMap; 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 8be92b7913..5f0e8664c8 100644 --- a/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr +++ b/axum-macros/tests/debug_handler/fail/argument_not_extractor.stderr @@ -15,7 +15,7 @@ error[E0277]: the trait bound `bool: FromRequestParts<()>` is not satisfied <(T1, T2, T3, T4, T5, T6) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts> - and 26 others + and $N others = note: required for `bool` to implement `FromRequest<(), axum_core::extract::private::ViaParts>` note: required by a bound in `__axum_macros_check_handler_0_from_request_check` --> tests/debug_handler/fail/argument_not_extractor.rs:4:23 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 151c5f84bf..46d4673b75 100644 --- a/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr +++ b/axum-macros/tests/debug_handler/fail/wrong_return_type.stderr @@ -18,4 +18,4 @@ note: required by a bound in `__axum_macros_check_handler_into_response::{closur --> tests/debug_handler/fail/wrong_return_type.rs:4:23 | 4 | async fn handler() -> bool { - | ^^^^ required by this bound in `__axum_macros_check_handler_into_response::{closure#0}::check` + | ^^^^ required by this bound in `check` diff --git a/axum-macros/tests/from_request/fail/generic_without_via.stderr b/axum-macros/tests/from_request/fail/generic_without_via.stderr index e875b443b7..961d6a5b35 100644 --- a/axum-macros/tests/from_request/fail/generic_without_via.stderr +++ b/axum-macros/tests/from_request/fail/generic_without_via.stderr @@ -20,5 +20,5 @@ note: required by a bound in `axum::routing::get` --> $WORKSPACE/axum/src/routing/method_routing.rs | | top_level_handler_fn!(get, GET); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `get` = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/axum-macros/tests/from_request/fail/generic_without_via_rejection.stderr b/axum-macros/tests/from_request/fail/generic_without_via_rejection.stderr index 167b4a3681..00eb8d09aa 100644 --- a/axum-macros/tests/from_request/fail/generic_without_via_rejection.stderr +++ b/axum-macros/tests/from_request/fail/generic_without_via_rejection.stderr @@ -20,5 +20,5 @@ note: required by a bound in `axum::routing::get` --> $WORKSPACE/axum/src/routing/method_routing.rs | | top_level_handler_fn!(get, GET); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `get` = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/axum-macros/tests/from_request/fail/override_rejection_on_enum_without_via.stderr b/axum-macros/tests/from_request/fail/override_rejection_on_enum_without_via.stderr index 7ed7dea626..ca8353b2b7 100644 --- a/axum-macros/tests/from_request/fail/override_rejection_on_enum_without_via.stderr +++ b/axum-macros/tests/from_request/fail/override_rejection_on_enum_without_via.stderr @@ -5,39 +5,39 @@ error: cannot use `rejection` without `via` | ^^^^^^^^^ error[E0277]: the trait bound `fn(MyExtractor) -> impl Future {handler}: Handler<_, _>` is not satisfied - --> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:50 - | -10 | let _: Router = Router::new().route("/", get(handler).post(handler_result)); - | --- ^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(MyExtractor) -> impl Future {handler}` - | | - | required by a bound introduced by this call - | - = note: Consider using `#[axum::debug_handler]` to improve the error message - = help: the following other types implement trait `Handler`: - as Handler> - as Handler<(), S>> + --> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:50 + | +10 | let _: Router = Router::new().route("/", get(handler).post(handler_result)); + | --- ^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(MyExtractor) -> impl Future {handler}` + | | + | required by a bound introduced by this call + | + = note: Consider using `#[axum::debug_handler]` to improve the error message + = help: the following other types implement trait `Handler`: + as Handler> + as Handler<(), S>> note: required by a bound in `axum::routing::get` - --> $WORKSPACE/axum/src/routing/method_routing.rs - | - | top_level_handler_fn!(get, GET); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get` - = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) + --> $WORKSPACE/axum/src/routing/method_routing.rs + | + | top_level_handler_fn!(get, GET); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `get` + = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `fn(Result) -> impl Future {handler_result}: Handler<_, _>` is not satisfied - --> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:64 - | -10 | let _: Router = Router::new().route("/", get(handler).post(handler_result)); - | ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Result) -> impl Future {handler_result}` - | | - | required by a bound introduced by this call - | - = note: Consider using `#[axum::debug_handler]` to improve the error message - = help: the following other types implement trait `Handler`: - as Handler> - as Handler<(), S>> + --> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:64 + | +10 | let _: Router = Router::new().route("/", get(handler).post(handler_result)); + | ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Result) -> impl Future {handler_result}` + | | + | required by a bound introduced by this call + | + = note: Consider using `#[axum::debug_handler]` to improve the error message + = help: the following other types implement trait `Handler`: + as Handler> + as Handler<(), S>> note: required by a bound in `MethodRouter::::post` - --> $WORKSPACE/axum/src/routing/method_routing.rs - | - | chained_handler_fn!(post, POST); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::::post` - = note: this error originates in the macro `chained_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) + --> $WORKSPACE/axum/src/routing/method_routing.rs + | + | chained_handler_fn!(post, POST); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::::post` + = note: this error originates in the macro `chained_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/axum-macros/tests/from_request/fail/parts_extracting_body.stderr b/axum-macros/tests/from_request/fail/parts_extracting_body.stderr index 54c0dc0222..896cf2d991 100644 --- a/axum-macros/tests/from_request/fail/parts_extracting_body.stderr +++ b/axum-macros/tests/from_request/fail/parts_extracting_body.stderr @@ -15,4 +15,4 @@ error[E0277]: the trait bound `String: FromRequestParts` is not satisfied <(T1, T2, T3, T4, T5, T6) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts> <(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts> - and 27 others + and $N others diff --git a/axum-macros/tests/from_request/pass/named.rs b/axum-macros/tests/from_request/pass/named.rs index e775d41d84..f63ae8e9db 100644 --- a/axum-macros/tests/from_request/pass/named.rs +++ b/axum-macros/tests/from_request/pass/named.rs @@ -1,6 +1,10 @@ use axum::{ - extract::{FromRequest, TypedHeader, rejection::TypedHeaderRejection}, + extract::FromRequest, response::Response, +}; +use axum_extra::{ + TypedHeader, + typed_header::TypedHeaderRejection, headers::{self, UserAgent}, }; diff --git a/axum-macros/tests/from_request/pass/named_parts.rs b/axum-macros/tests/from_request/pass/named_parts.rs index 27dce64f21..cbb67e61da 100644 --- a/axum-macros/tests/from_request/pass/named_parts.rs +++ b/axum-macros/tests/from_request/pass/named_parts.rs @@ -1,8 +1,12 @@ use axum::{ - extract::{rejection::TypedHeaderRejection, FromRequestParts, TypedHeader}, - headers::{self, UserAgent}, + extract::FromRequestParts, response::Response, }; +use axum_extra::{ + TypedHeader, + typed_header::TypedHeaderRejection, + headers::{self, UserAgent}, +}; #[derive(FromRequestParts)] struct Extractor { diff --git a/axum-macros/tests/from_request/pass/named_via.rs b/axum-macros/tests/from_request/pass/named_via.rs index be2e8c67a6..691627b08d 100644 --- a/axum-macros/tests/from_request/pass/named_via.rs +++ b/axum-macros/tests/from_request/pass/named_via.rs @@ -1,9 +1,10 @@ use axum::{ response::Response, - extract::{ - rejection::TypedHeaderRejection, - Extension, FromRequest, TypedHeader, - }, + extract::{Extension, FromRequest}, +}; +use axum_extra::{ + TypedHeader, + typed_header::TypedHeaderRejection, headers::{self, UserAgent}, }; diff --git a/axum-macros/tests/from_request/pass/named_via_parts.rs b/axum-macros/tests/from_request/pass/named_via_parts.rs index 9a389e549b..0377af7b10 100644 --- a/axum-macros/tests/from_request/pass/named_via_parts.rs +++ b/axum-macros/tests/from_request/pass/named_via_parts.rs @@ -1,9 +1,10 @@ use axum::{ response::Response, - extract::{ - rejection::TypedHeaderRejection, - Extension, FromRequestParts, TypedHeader, - }, + extract::{Extension, FromRequestParts}, +}; +use axum_extra::{ + TypedHeader, + typed_header::TypedHeaderRejection, headers::{self, UserAgent}, }; diff --git a/axum-macros/tests/from_request/pass/override_rejection.rs b/axum-macros/tests/from_request/pass/override_rejection.rs index 779058b9fc..25e399b4e0 100644 --- a/axum-macros/tests/from_request/pass/override_rejection.rs +++ b/axum-macros/tests/from_request/pass/override_rejection.rs @@ -4,7 +4,6 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, routing::get, - body::Body, Extension, Router, }; diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index c7f33b397f..1d4aaea99e 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **breaking:** Change `sse::Event::json_data` to use `axum_core::Error` as its error type ([#1762]) - **breaking:** Rename `DefaultOnFailedUpdgrade` to `DefaultOnFailedUpgrade` ([#1664]) - **breaking:** Rename `OnFailedUpdgrade` to `OnFailedUpgrade` ([#1664]) +- **breaking:** `TypedHeader` has been move to `axum-extra` ([#1850]) - **breaking:** Removed re-exports of `Empty` and `Full`. Use `axum::body::Body::empty` and `axum::body::Body::from` respectively ([#1789]) - **breaking:** The response returned by `IntoResponse::into_response` must use @@ -53,8 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#1664]: https://github.com/tokio-rs/axum/pull/1664 [#1751]: https://github.com/tokio-rs/axum/pull/1751 [#1762]: https://github.com/tokio-rs/axum/pull/1762 -[#1835]: https://github.com/tokio-rs/axum/pull/1835 [#1789]: https://github.com/tokio-rs/axum/pull/1789 +[#1835]: https://github.com/tokio-rs/axum/pull/1835 +[#1850]: https://github.com/tokio-rs/axum/pull/1850 [#1868]: https://github.com/tokio-rs/axum/pull/1868 # 0.6.12 (22. March, 2023) diff --git a/axum/Cargo.toml b/axum/Cargo.toml index 43a7f04add..9c3ba4c9d1 100644 --- a/axum/Cargo.toml +++ b/axum/Cargo.toml @@ -57,7 +57,6 @@ tower-hyper-http-body-compat = { version = "0.1.4", features = ["server", "http1 # optional dependencies axum-macros = { path = "../axum-macros", version = "0.3.7", optional = true } base64 = { version = "0.21.0", optional = true } -headers = { version = "0.3.7", optional = true } multer = { version = "2.0.0", optional = true } serde_json = { version = "1.0", features = ["raw_value"], optional = true } serde_path_to_error = { version = "0.1.8", optional = true } @@ -180,12 +179,13 @@ features = [ [package.metadata.cargo-public-api-crates] allowed = [ + "async_trait", "axum_core", + "axum_macros", "bytes", "futures_core", "futures_sink", "futures_util", - "headers_core", "http", "http_body", "hyper", diff --git a/axum/src/docs/extract.md b/axum/src/docs/extract.md index 71525b878a..ce9cab7527 100644 --- a/axum/src/docs/extract.md +++ b/axum/src/docs/extract.md @@ -13,7 +13,6 @@ Types and traits for extracting data from requests. - [Accessing other extractors in `FromRequest` or `FromRequestParts` implementations](#accessing-other-extractors-in-fromrequest-or-fromrequestparts-implementations) - [Request body limits](#request-body-limits) - [Request body extractors](#request-body-extractors) -- [Running extractors from middleware](#running-extractors-from-middleware) - [Wrapping extractors](#wrapping-extractors) # Intro @@ -55,9 +54,8 @@ Some commonly used extractors are: ```rust,no_run use axum::{ - extract::{Request, Json, TypedHeader, Path, Extension, Query}, + extract::{Request, Json, Path, Extension, Query}, routing::post, - headers::UserAgent, http::header::HeaderMap, body::{Bytes, Body}, Router, @@ -75,10 +73,6 @@ async fn query(Query(params): Query>) {} // `HeaderMap` gives you all the headers async fn headers(headers: HeaderMap) {} -// `TypedHeader` can be used to extract a single header -// note this requires you've enabled axum's `headers` feature -async fn user_agent(TypedHeader(user_agent): TypedHeader) {} - // `String` consumes the request body and ensures it is valid utf-8 async fn string(body: String) {} @@ -101,8 +95,6 @@ struct State { /* ... */ } let app = Router::new() .route("/path/:user_id", post(path)) .route("/query", post(query)) - .route("/user_agent", post(user_agent)) - .route("/headers", post(headers)) .route("/string", post(string)) .route("/bytes", post(bytes)) .route("/json", post(json)) @@ -561,9 +553,8 @@ in your implementation. ```rust use axum::{ async_trait, - extract::{Extension, FromRequestParts, TypedHeader}, - headers::{authorization::Bearer, Authorization}, - http::{StatusCode, request::Parts}, + extract::{Extension, FromRequestParts}, + http::{StatusCode, HeaderMap, request::Parts}, response::{IntoResponse, Response}, routing::get, Router, @@ -587,10 +578,9 @@ where async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { // You can either call them directly... - let TypedHeader(Authorization(token)) = - TypedHeader::>::from_request_parts(parts, state) - .await - .map_err(|err| err.into_response())?; + let headers = HeaderMap::from_request_parts(parts, state) + .await + .map_err(|err| match err {})?; // ... or use `extract` / `extract_with_state` from `RequestExt` / `RequestPartsExt` use axum::RequestPartsExt; @@ -620,51 +610,6 @@ For security reasons, [`Bytes`] will, by default, not accept bodies larger than For more details, including how to disable this limit, see [`DefaultBodyLimit`]. -# Running extractors from middleware - -Extractors can also be run from middleware: - -```rust -use axum::{ - middleware::{self, Next}, - extract::{TypedHeader, Request, FromRequestParts}, - http::StatusCode, - response::Response, - headers::authorization::{Authorization, Bearer}, - RequestPartsExt, Router, -}; - -async fn auth_middleware( - request: Request, - next: Next, -) -> Result { - // running extractors requires a `axum::http::request::Parts` - let (mut parts, body) = request.into_parts(); - - // `TypedHeader>` extracts the auth token - let auth: TypedHeader> = parts.extract() - .await - .map_err(|_| StatusCode::UNAUTHORIZED)?; - - if !token_is_valid(auth.token()) { - return Err(StatusCode::UNAUTHORIZED); - } - - // reconstruct the request - let request = Request::from_parts(parts, body); - - 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; -``` - # Wrapping extractors If you want write an extractor that generically wraps another extractor (that diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 19d46cb5e6..fce1b01081 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -76,10 +76,6 @@ pub use self::request_parts::OriginalUri; #[doc(inline)] pub use self::ws::WebSocketUpgrade; -#[cfg(feature = "headers")] -#[doc(no_inline)] -pub use crate::TypedHeader; - // this is duplicated in `axum-extra/src/extract/form.rs` pub(super) fn has_content_type(headers: &HeaderMap, expected_content_type: &mime::Mime) -> bool { let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) { diff --git a/axum/src/extract/rejection.rs b/axum/src/extract/rejection.rs index 7285d49ef7..d7125683ad 100644 --- a/axum/src/extract/rejection.rs +++ b/axum/src/extract/rejection.rs @@ -204,6 +204,3 @@ composite_rejection! { MatchedPathMissing, } } - -#[cfg(feature = "headers")] -pub use crate::typed_header::{TypedHeaderRejection, TypedHeaderRejectionReason}; diff --git a/axum/src/lib.rs b/axum/src/lib.rs index 9dca510fc1..ae3a31810d 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -336,7 +336,6 @@ //! //! Name | Description | Default? //! ---|---|--- -//! `headers` | Enables extracting typed headers via [`TypedHeader`] | No //! `http1` | Enables hyper's `http1` feature | Yes //! `http2` | Enables hyper's `http2` feature | No //! `json` | Enables the [`Json`] type and some similar convenience functionality | Yes @@ -350,7 +349,6 @@ //! `form` | Enables the `Form` extractor | Yes //! `query` | Enables the `Query` extractor | Yes //! -//! [`TypedHeader`]: crate::extract::TypedHeader //! [`MatchedPath`]: crate::extract::MatchedPath //! [`Multipart`]: crate::extract::Multipart //! [`OriginalUri`]: crate::extract::OriginalUri @@ -434,8 +432,6 @@ mod form; #[cfg(feature = "json")] mod json; mod service_ext; -#[cfg(feature = "headers")] -mod typed_header; mod util; pub mod body; @@ -453,9 +449,6 @@ mod test_helpers; #[doc(no_inline)] pub use async_trait::async_trait; -#[cfg(feature = "headers")] -#[doc(no_inline)] -pub use headers; #[doc(no_inline)] pub use http; @@ -467,10 +460,6 @@ pub use self::json::Json; #[doc(inline)] pub use self::routing::Router; -#[doc(inline)] -#[cfg(feature = "headers")] -pub use self::typed_header::TypedHeader; - #[doc(inline)] #[cfg(feature = "form")] pub use self::form::Form; diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs index c2cf5fe659..b47a11a3e8 100644 --- a/axum/src/middleware/from_fn.rs +++ b/axum/src/middleware/from_fn.rs @@ -61,31 +61,38 @@ use tower_service::Service; /// ```rust /// use axum::{ /// Router, -/// extract::{Request, TypedHeader}, -/// http::StatusCode, -/// headers::authorization::{Authorization, Bearer}, +/// extract::Request, +/// http::{StatusCode, HeaderMap}, /// middleware::{self, Next}, /// response::Response, /// routing::get, /// }; /// /// async fn auth( -/// // run the `TypedHeader` extractor -/// TypedHeader(auth): TypedHeader>, +/// // run the `HeaderMap` extractor +/// headers: HeaderMap, /// // you can also add more extractors here but the last /// // extractor must implement `FromRequest` which /// // `Request` does /// request: Request, /// next: Next, /// ) -> Result { -/// if token_is_valid(auth.token()) { -/// let response = next.run(request).await; -/// Ok(response) -/// } else { -/// Err(StatusCode::UNAUTHORIZED) +/// match get_token(&headers) { +/// Some(token) if token_is_valid(token) => { +/// let response = next.run(request).await; +/// Ok(response) +/// } +/// _ => { +/// Err(StatusCode::UNAUTHORIZED) +/// } /// } /// } /// +/// fn get_token(headers: &HeaderMap) -> Option<&str> { +/// // ... +/// # None +/// } +/// /// fn token_is_valid(token: &str) -> bool { /// // ... /// # false diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index 919ca78b1c..6cfd9b0763 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -12,10 +12,6 @@ pub mod sse; #[cfg(feature = "json")] pub use crate::Json; -#[doc(no_inline)] -#[cfg(feature = "headers")] -pub use crate::TypedHeader; - #[cfg(feature = "form")] #[doc(no_inline)] pub use crate::form::Form; diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index 44c257d4c4..f6c1380538 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -841,10 +841,10 @@ where fn layer(self, layer: L) -> Endpoint where L: Layer + Clone + Send + 'static, - L::Service: Service + Clone + Send + 'static, - >::Response: IntoResponse + 'static, - >::Error: Into + 'static, - >::Future: Send + 'static, + L::Service: Service> + Clone + Send + 'static, + >>::Response: IntoResponse + 'static, + >>::Error: Into + 'static, + >>::Future: Send + 'static, { match self { Endpoint::MethodRouter(method_router) => { diff --git a/examples/jwt/Cargo.toml b/examples/jwt/Cargo.toml index a18eb6ec78..b0c76c25d1 100644 --- a/examples/jwt/Cargo.toml +++ b/examples/jwt/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" publish = false [dependencies] -axum = { path = "../../axum", features = ["headers"] } -headers = "0.3" +axum = { path = "../../axum" } +axum-extra = { path = "../../axum-extra", features = ["typed-header"] } jsonwebtoken = "8.0" once_cell = "1.8" serde = { version = "1.0", features = ["derive"] } diff --git a/examples/jwt/src/main.rs b/examples/jwt/src/main.rs index a6f8bffa4c..dda09d636e 100644 --- a/examples/jwt/src/main.rs +++ b/examples/jwt/src/main.rs @@ -8,13 +8,16 @@ use axum::{ async_trait, - extract::{FromRequestParts, TypedHeader}, - headers::{authorization::Bearer, Authorization}, + extract::FromRequestParts, http::{request::Parts, StatusCode}, response::{IntoResponse, Response}, routing::{get, post}, Json, RequestPartsExt, Router, }; +use axum_extra::{ + headers::{authorization::Bearer, Authorization}, + TypedHeader, +}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; diff --git a/examples/oauth/Cargo.toml b/examples/oauth/Cargo.toml index 6613367379..bb0aafc92a 100644 --- a/examples/oauth/Cargo.toml +++ b/examples/oauth/Cargo.toml @@ -6,8 +6,8 @@ publish = false [dependencies] async-session = "3.0.0" -axum = { path = "../../axum", features = ["headers"] } -headers = "0.3" +axum = { path = "../../axum" } +axum-extra = { path = "../../axum-extra", features = ["typed-header"] } http = "0.2" oauth2 = "4.1" # Use Rustls because it makes it easier to cross-compile on CI diff --git a/examples/oauth/src/main.rs b/examples/oauth/src/main.rs index 40a039bcd8..ba879e7be8 100644 --- a/examples/oauth/src/main.rs +++ b/examples/oauth/src/main.rs @@ -11,14 +11,13 @@ use async_session::{MemoryStore, Session, SessionStore}; use axum::{ async_trait, - extract::{ - rejection::TypedHeaderRejectionReason, FromRef, FromRequestParts, Query, State, TypedHeader, - }, + extract::{FromRef, FromRequestParts, Query, State}, http::{header::SET_COOKIE, HeaderMap}, response::{IntoResponse, Redirect, Response}, routing::get, RequestPartsExt, Router, }; +use axum_extra::{headers, typed_header::TypedHeaderRejectionReason, TypedHeader}; use http::{header, request::Parts}; use oauth2::{ basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId, diff --git a/examples/sessions/Cargo.toml b/examples/sessions/Cargo.toml index 247df4d3c6..bb3d4bda1c 100644 --- a/examples/sessions/Cargo.toml +++ b/examples/sessions/Cargo.toml @@ -6,7 +6,8 @@ publish = false [dependencies] async-session = "3.0.0" -axum = { path = "../../axum", features = ["headers"] } +axum = { path = "../../axum" } +axum-extra = { path = "../../axum-extra", features = ["typed-header"] } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } tracing = "0.1" diff --git a/examples/sessions/src/main.rs b/examples/sessions/src/main.rs index fcac6c5670..8ee8d9d43e 100644 --- a/examples/sessions/src/main.rs +++ b/examples/sessions/src/main.rs @@ -7,8 +7,7 @@ use async_session::{MemoryStore, Session, SessionStore as _}; use axum::{ async_trait, - extract::{FromRef, FromRequestParts, TypedHeader}, - headers::Cookie, + extract::{FromRef, FromRequestParts}, http::{ self, header::{HeaderMap, HeaderValue}, @@ -19,6 +18,7 @@ use axum::{ routing::get, RequestPartsExt, Router, }; +use axum_extra::{headers::Cookie, TypedHeader}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; diff --git a/examples/sse/Cargo.toml b/examples/sse/Cargo.toml index c0e12979ff..0131991d8a 100644 --- a/examples/sse/Cargo.toml +++ b/examples/sse/Cargo.toml @@ -5,7 +5,8 @@ edition = "2021" publish = false [dependencies] -axum = { path = "../../axum", features = ["headers"] } +axum = { path = "../../axum" } +axum-extra = { path = "../../axum-extra", features = ["typed-header"] } futures = "0.3" headers = "0.3" tokio = { version = "1.0", features = ["full"] } diff --git a/examples/sse/src/main.rs b/examples/sse/src/main.rs index 528881337c..bb60cedf00 100644 --- a/examples/sse/src/main.rs +++ b/examples/sse/src/main.rs @@ -5,11 +5,11 @@ //! ``` use axum::{ - extract::TypedHeader, response::sse::{Event, Sse}, routing::get, Router, }; +use axum_extra::{headers, TypedHeader}; use futures::stream::{self, Stream}; use std::{convert::Infallible, path::PathBuf, time::Duration}; use tokio_stream::StreamExt as _; diff --git a/examples/websockets/Cargo.toml b/examples/websockets/Cargo.toml index 30e6c3c0f1..c385b78944 100644 --- a/examples/websockets/Cargo.toml +++ b/examples/websockets/Cargo.toml @@ -5,7 +5,8 @@ edition = "2021" publish = false [dependencies] -axum = { path = "../../axum", features = ["ws", "headers"] } +axum = { path = "../../axum", features = ["ws"] } +axum-extra = { path = "../../axum-extra", features = ["typed-header"] } futures = "0.3" futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } headers = "0.3" diff --git a/examples/websockets/src/main.rs b/examples/websockets/src/main.rs index e13182bbb9..1a7a9bd46e 100644 --- a/examples/websockets/src/main.rs +++ b/examples/websockets/src/main.rs @@ -17,14 +17,12 @@ //! ``` use axum::{ - extract::{ - ws::{Message, WebSocket, WebSocketUpgrade}, - TypedHeader, - }, + extract::ws::{Message, WebSocket, WebSocketUpgrade}, response::IntoResponse, routing::get, Router, }; +use axum_extra::TypedHeader; use std::borrow::Cow; use std::ops::ControlFlow;