mirror of
https://github.com/tokio-rs/axum.git
synced 2025-09-28 21:40:55 +00:00
Move TypedHeader
to axum-extra (#1850)
Co-authored-by: Michael Scofield <mscofield0@tutanota.com> Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
This commit is contained in:
parent
173f9f72b0
commit
877e3fe4de
@ -37,7 +37,8 @@ tracing = { version = "0.1.37", default-features = false, optional = true }
|
|||||||
rustversion = "1.0.9"
|
rustversion = "1.0.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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"] }
|
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
|
||||||
hyper = "0.14.24"
|
hyper = "0.14.24"
|
||||||
tokio = { version = "1.25.0", features = ["macros"] }
|
tokio = { version = "1.25.0", features = ["macros"] }
|
||||||
|
@ -139,15 +139,19 @@ pub trait RequestExt: sealed::Sealed + Sized {
|
|||||||
/// ```
|
/// ```
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// async_trait,
|
/// async_trait,
|
||||||
/// extract::{Request, FromRequest},
|
/// extract::{Path, Request, FromRequest},
|
||||||
/// headers::{authorization::Bearer, Authorization},
|
|
||||||
/// response::{IntoResponse, Response},
|
/// response::{IntoResponse, Response},
|
||||||
/// body::Body,
|
/// body::Body,
|
||||||
/// Json, RequestExt, TypedHeader,
|
/// Json, RequestExt,
|
||||||
/// };
|
/// };
|
||||||
|
/// use axum_extra::{
|
||||||
|
/// TypedHeader,
|
||||||
|
/// headers::{authorization::Bearer, Authorization},
|
||||||
|
/// };
|
||||||
|
/// use std::collections::HashMap;
|
||||||
///
|
///
|
||||||
/// struct MyExtractor<T> {
|
/// struct MyExtractor<T> {
|
||||||
/// bearer_token: String,
|
/// path_params: HashMap<String, String>,
|
||||||
/// payload: T,
|
/// payload: T,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -161,9 +165,10 @@ pub trait RequestExt: sealed::Sealed + Sized {
|
|||||||
/// type Rejection = Response;
|
/// type Rejection = Response;
|
||||||
///
|
///
|
||||||
/// async fn from_request(mut req: Request, _state: &S) -> Result<Self, Self::Rejection> {
|
/// async fn from_request(mut req: Request, _state: &S) -> Result<Self, Self::Rejection> {
|
||||||
/// let TypedHeader(auth_header) = req
|
/// let path_params = req
|
||||||
/// .extract_parts::<TypedHeader<Authorization<Bearer>>>()
|
/// .extract_parts::<Path<_>>()
|
||||||
/// .await
|
/// .await
|
||||||
|
/// .map(|Path(path_params)| path_params)
|
||||||
/// .map_err(|err| err.into_response())?;
|
/// .map_err(|err| err.into_response())?;
|
||||||
///
|
///
|
||||||
/// let Json(payload) = req
|
/// let Json(payload) = req
|
||||||
@ -171,10 +176,7 @@ pub trait RequestExt: sealed::Sealed + Sized {
|
|||||||
/// .await
|
/// .await
|
||||||
/// .map_err(|err| err.into_response())?;
|
/// .map_err(|err| err.into_response())?;
|
||||||
///
|
///
|
||||||
/// Ok(Self {
|
/// Ok(Self { path_params, payload })
|
||||||
/// bearer_token: auth_header.token().to_owned(),
|
|
||||||
/// payload,
|
|
||||||
/// })
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -17,9 +17,8 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// extract::{Query, TypedHeader, FromRequestParts},
|
/// extract::{Query, Path, FromRequestParts},
|
||||||
/// response::{Response, IntoResponse},
|
/// response::{Response, IntoResponse},
|
||||||
/// headers::UserAgent,
|
|
||||||
/// http::request::Parts,
|
/// http::request::Parts,
|
||||||
/// RequestPartsExt,
|
/// RequestPartsExt,
|
||||||
/// async_trait,
|
/// async_trait,
|
||||||
@ -27,7 +26,7 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
|
|||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
///
|
///
|
||||||
/// struct MyExtractor {
|
/// struct MyExtractor {
|
||||||
/// user_agent: String,
|
/// path_params: HashMap<String, String>,
|
||||||
/// query_params: HashMap<String, String>,
|
/// query_params: HashMap<String, String>,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -39,10 +38,10 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
|
|||||||
/// type Rejection = Response;
|
/// type Rejection = Response;
|
||||||
///
|
///
|
||||||
/// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
/// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
/// let user_agent = parts
|
/// let path_params = parts
|
||||||
/// .extract::<TypedHeader<UserAgent>>()
|
/// .extract::<Path<HashMap<String, String>>>()
|
||||||
/// .await
|
/// .await
|
||||||
/// .map(|user_agent| user_agent.as_str().to_owned())
|
/// .map(|Path(path_params)| path_params)
|
||||||
/// .map_err(|err| err.into_response())?;
|
/// .map_err(|err| err.into_response())?;
|
||||||
///
|
///
|
||||||
/// let query_params = parts
|
/// let query_params = parts
|
||||||
@ -51,7 +50,7 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
|
|||||||
/// .map(|Query(params)| params)
|
/// .map(|Query(params)| params)
|
||||||
/// .map_err(|err| err.into_response())?;
|
/// .map_err(|err| err.into_response())?;
|
||||||
///
|
///
|
||||||
/// Ok(MyExtractor { user_agent, query_params })
|
/// Ok(MyExtractor { path_params, query_params })
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning].
|
|||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- None.
|
- **added:** Added `TypedHeader` which used to be in `axum` ([#1850])
|
||||||
|
|
||||||
|
[#1850]: https://github.com/tokio-rs/axum/pull/1850
|
||||||
|
|
||||||
# 0.7.4 (18. April, 2023)
|
# 0.7.4 (18. April, 2023)
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ json-lines = [
|
|||||||
multipart = ["dep:multer"]
|
multipart = ["dep:multer"]
|
||||||
protobuf = ["dep:prost"]
|
protobuf = ["dep:prost"]
|
||||||
query = ["dep:serde_html_form"]
|
query = ["dep:serde_html_form"]
|
||||||
|
typed-header = ["dep:headers"]
|
||||||
typed-routing = ["dep:axum-macros", "dep:percent-encoding", "dep:serde_html_form", "dep:form_urlencoded"]
|
typed-routing = ["dep:axum-macros", "dep:percent-encoding", "dep:serde_html_form", "dep:form_urlencoded"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -53,6 +54,7 @@ tower-service = "0.3"
|
|||||||
axum-macros = { path = "../axum-macros", version = "0.3.7", optional = true }
|
axum-macros = { path = "../axum-macros", version = "0.3.7", optional = true }
|
||||||
cookie = { package = "cookie", version = "0.17", features = ["percent-encode"], optional = true }
|
cookie = { package = "cookie", version = "0.17", features = ["percent-encode"], optional = true }
|
||||||
form_urlencoded = { version = "1.1.0", optional = true }
|
form_urlencoded = { version = "1.1.0", optional = true }
|
||||||
|
headers = { version = "0.3.8", optional = true }
|
||||||
multer = { version = "2.0.0", optional = true }
|
multer = { version = "2.0.0", optional = true }
|
||||||
percent-encoding = { version = "2.1", optional = true }
|
percent-encoding = { version = "2.1", optional = true }
|
||||||
prost = { version = "0.11", optional = true }
|
prost = { version = "0.11", optional = true }
|
||||||
@ -62,8 +64,8 @@ tokio-stream = { version = "0.1.9", optional = true }
|
|||||||
tokio-util = { version = "0.7", optional = true }
|
tokio-util = { version = "0.7", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum = { path = "../axum", version = "0.6.0", features = ["headers"] }
|
axum = { path = "../axum", version = "0.6.0" }
|
||||||
axum-macros = { path = "../axum-macros", version = "0.3.7", features = ["__private"] }
|
futures = "0.3"
|
||||||
http-body = "0.4.4"
|
http-body = "0.4.4"
|
||||||
hyper = "0.14"
|
hyper = "0.14"
|
||||||
reqwest = { version = "0.11", default-features = false, features = ["json", "stream", "multipart"] }
|
reqwest = { version = "0.11", default-features = false, features = ["json", "stream", "multipart"] }
|
||||||
@ -86,9 +88,10 @@ allowed = [
|
|||||||
"cookie",
|
"cookie",
|
||||||
"futures_core",
|
"futures_core",
|
||||||
"futures_util",
|
"futures_util",
|
||||||
|
"headers",
|
||||||
|
"headers_core",
|
||||||
"http",
|
"http",
|
||||||
"http_body",
|
"http_body",
|
||||||
"hyper",
|
|
||||||
"prost",
|
"prost",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -41,12 +41,14 @@ pub use cookie::Key;
|
|||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// Router,
|
/// Router,
|
||||||
/// routing::{post, get},
|
/// routing::{post, get},
|
||||||
/// extract::TypedHeader,
|
|
||||||
/// response::{IntoResponse, Redirect},
|
/// response::{IntoResponse, Redirect},
|
||||||
/// headers::authorization::{Authorization, Bearer},
|
|
||||||
/// http::StatusCode,
|
/// 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(
|
/// async fn create_session(
|
||||||
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||||
|
@ -23,12 +23,15 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// Router,
|
/// Router,
|
||||||
/// routing::{post, get},
|
/// routing::{post, get},
|
||||||
/// extract::{TypedHeader, FromRef},
|
/// extract::FromRef,
|
||||||
/// response::{IntoResponse, Redirect},
|
/// response::{IntoResponse, Redirect},
|
||||||
/// headers::authorization::{Authorization, Bearer},
|
|
||||||
/// http::StatusCode,
|
/// 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(
|
/// async fn set_secret(
|
||||||
/// jar: PrivateCookieJar,
|
/// jar: PrivateCookieJar,
|
||||||
|
@ -24,12 +24,15 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// Router,
|
/// Router,
|
||||||
/// routing::{post, get},
|
/// routing::{post, get},
|
||||||
/// extract::{TypedHeader, FromRef},
|
/// extract::FromRef,
|
||||||
/// response::{IntoResponse, Redirect},
|
/// response::{IntoResponse, Redirect},
|
||||||
/// headers::authorization::{Authorization, Bearer},
|
|
||||||
/// http::StatusCode,
|
/// 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(
|
/// async fn create_session(
|
||||||
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||||
|
@ -39,3 +39,7 @@ pub use self::multipart::Multipart;
|
|||||||
#[cfg(feature = "json-lines")]
|
#[cfg(feature = "json-lines")]
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::json_lines::JsonLines;
|
pub use crate::json_lines::JsonLines;
|
||||||
|
|
||||||
|
#[cfg(feature = "typed-header")]
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use crate::typed_header::TypedHeader;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
//! `protobuf` | Enables the `Protobuf` extractor and response | No
|
//! `protobuf` | Enables the `Protobuf` extractor and response | No
|
||||||
//! `query` | Enables the `Query` extractor | No
|
//! `query` | Enables the `Query` extractor | No
|
||||||
//! `typed-routing` | Enables the `TypedPath` routing utilities | No
|
//! `typed-routing` | Enables the `TypedPath` routing utilities | No
|
||||||
|
//! `typed-header` | Enables the `TypedHeader` extractor and response | No
|
||||||
//!
|
//!
|
||||||
//! [`axum`]: https://crates.io/crates/axum
|
//! [`axum`]: https://crates.io/crates/axum
|
||||||
|
|
||||||
@ -80,6 +81,17 @@ pub mod routing;
|
|||||||
#[cfg(feature = "json-lines")]
|
#[cfg(feature = "json-lines")]
|
||||||
pub mod 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")]
|
#[cfg(feature = "protobuf")]
|
||||||
pub mod protobuf;
|
pub mod protobuf;
|
||||||
|
|
||||||
|
@ -88,3 +88,7 @@ mime_response! {
|
|||||||
Wasm,
|
Wasm,
|
||||||
"application/wasm",
|
"application/wasm",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "typed-header")]
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use crate::typed_header::TypedHeader;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
use crate::extract::FromRequestParts;
|
//! Extractor and response for typed headers.
|
||||||
use async_trait::async_trait;
|
|
||||||
use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts};
|
use axum::{
|
||||||
use headers::HeaderMapExt;
|
async_trait,
|
||||||
|
extract::FromRequestParts,
|
||||||
|
response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
|
||||||
|
};
|
||||||
|
use headers::{Header, HeaderMapExt};
|
||||||
use http::request::Parts;
|
use http::request::Parts;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
@ -14,11 +18,11 @@ use std::convert::Infallible;
|
|||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// TypedHeader,
|
|
||||||
/// headers::UserAgent,
|
|
||||||
/// routing::get,
|
/// routing::get,
|
||||||
/// Router,
|
/// Router,
|
||||||
/// };
|
/// };
|
||||||
|
/// use headers::UserAgent;
|
||||||
|
/// use axum_extra::TypedHeader;
|
||||||
///
|
///
|
||||||
/// async fn users_teams_show(
|
/// async fn users_teams_show(
|
||||||
/// TypedHeader(user_agent): TypedHeader<UserAgent>,
|
/// TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||||
@ -34,10 +38,10 @@ use std::convert::Infallible;
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// TypedHeader,
|
|
||||||
/// response::IntoResponse,
|
/// response::IntoResponse,
|
||||||
/// headers::ContentType,
|
|
||||||
/// };
|
/// };
|
||||||
|
/// use headers::ContentType;
|
||||||
|
/// use axum_extra::TypedHeader;
|
||||||
///
|
///
|
||||||
/// async fn handler() -> (TypedHeader<ContentType>, &'static str) {
|
/// async fn handler() -> (TypedHeader<ContentType>, &'static str) {
|
||||||
/// (
|
/// (
|
||||||
@ -46,7 +50,7 @@ use std::convert::Infallible;
|
|||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(feature = "headers")]
|
#[cfg(feature = "typed-header")]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct TypedHeader<T>(pub T);
|
pub struct TypedHeader<T>(pub T);
|
||||||
@ -54,7 +58,7 @@ pub struct TypedHeader<T>(pub T);
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T, S> FromRequestParts<S> for TypedHeader<T>
|
impl<T, S> FromRequestParts<S> for TypedHeader<T>
|
||||||
where
|
where
|
||||||
T: headers::Header,
|
T: Header,
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
{
|
{
|
||||||
type Rejection = TypedHeaderRejection;
|
type Rejection = TypedHeaderRejection;
|
||||||
@ -80,7 +84,7 @@ axum_core::__impl_deref!(TypedHeader);
|
|||||||
|
|
||||||
impl<T> IntoResponseParts for TypedHeader<T>
|
impl<T> IntoResponseParts for TypedHeader<T>
|
||||||
where
|
where
|
||||||
T: headers::Header,
|
T: Header,
|
||||||
{
|
{
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
|
|
||||||
@ -92,7 +96,7 @@ where
|
|||||||
|
|
||||||
impl<T> IntoResponse for TypedHeader<T>
|
impl<T> IntoResponse for TypedHeader<T>
|
||||||
where
|
where
|
||||||
T: headers::Header,
|
T: Header,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
let mut res = ().into_response();
|
let mut res = ().into_response();
|
||||||
@ -101,8 +105,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rejection used for [`TypedHeader`](super::TypedHeader).
|
/// Rejection used for [`TypedHeader`](TypedHeader).
|
||||||
#[cfg(feature = "headers")]
|
#[cfg(feature = "typed-header")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TypedHeaderRejection {
|
pub struct TypedHeaderRejection {
|
||||||
name: &'static http::header::HeaderName,
|
name: &'static http::header::HeaderName,
|
||||||
@ -122,7 +126,7 @@ impl TypedHeaderRejection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Additional information regarding a [`TypedHeaderRejection`]
|
/// Additional information regarding a [`TypedHeaderRejection`]
|
||||||
#[cfg(feature = "headers")]
|
#[cfg(feature = "typed-header")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum TypedHeaderRejectionReason {
|
pub enum TypedHeaderRejectionReason {
|
||||||
@ -163,9 +167,10 @@ impl std::error::Error for TypedHeaderRejection {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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 typed_header() {
|
||||||
async fn handle(
|
async fn handle(
|
||||||
TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
|
TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
|
@ -30,8 +30,8 @@ syn = { version = "2.0", features = [
|
|||||||
] }
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum = { path = "../axum", version = "0.6.0", features = ["headers", "macros"] }
|
axum = { path = "../axum", version = "0.6.0", features = ["macros"] }
|
||||||
axum-extra = { path = "../axum-extra", version = "0.7.0", features = ["typed-routing", "cookie-private"] }
|
axum-extra = { path = "../axum-extra", version = "0.7.0", features = ["typed-routing", "cookie-private", "typed-header"] }
|
||||||
rustversion = "1.0"
|
rustversion = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
@ -1 +1 @@
|
|||||||
nightly-2022-11-18
|
nightly-2023-04-06
|
||||||
|
@ -72,10 +72,13 @@ use from_request::Trait::{FromRequest, FromRequestParts};
|
|||||||
/// ```
|
/// ```
|
||||||
/// use axum_macros::FromRequest;
|
/// use axum_macros::FromRequest;
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// extract::{Extension, TypedHeader},
|
/// extract::Extension,
|
||||||
/// headers::ContentType,
|
|
||||||
/// body::Bytes,
|
/// body::Bytes,
|
||||||
/// };
|
/// };
|
||||||
|
/// use axum_extra::{
|
||||||
|
/// TypedHeader,
|
||||||
|
/// headers::ContentType,
|
||||||
|
/// };
|
||||||
///
|
///
|
||||||
/// #[derive(FromRequest)]
|
/// #[derive(FromRequest)]
|
||||||
/// struct MyExtractor {
|
/// struct MyExtractor {
|
||||||
@ -117,10 +120,13 @@ use from_request::Trait::{FromRequest, FromRequestParts};
|
|||||||
/// ```
|
/// ```
|
||||||
/// use axum_macros::FromRequest;
|
/// use axum_macros::FromRequest;
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// extract::{Extension, TypedHeader},
|
/// extract::Extension,
|
||||||
/// headers::ContentType,
|
|
||||||
/// body::Bytes,
|
/// body::Bytes,
|
||||||
/// };
|
/// };
|
||||||
|
/// use axum_extra::{
|
||||||
|
/// TypedHeader,
|
||||||
|
/// headers::ContentType,
|
||||||
|
/// };
|
||||||
///
|
///
|
||||||
/// #[derive(FromRequest)]
|
/// #[derive(FromRequest)]
|
||||||
/// struct MyExtractor {
|
/// struct MyExtractor {
|
||||||
@ -159,9 +165,10 @@ use from_request::Trait::{FromRequest, FromRequestParts};
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use axum_macros::FromRequest;
|
/// use axum_macros::FromRequest;
|
||||||
/// use axum::{
|
/// use axum_extra::{
|
||||||
/// extract::{TypedHeader, rejection::TypedHeaderRejection},
|
/// TypedHeader,
|
||||||
/// headers::{ContentType, UserAgent},
|
/// headers::{ContentType, UserAgent},
|
||||||
|
/// typed_header::TypedHeaderRejection,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// #[derive(FromRequest)]
|
/// #[derive(FromRequest)]
|
||||||
@ -369,7 +376,10 @@ pub fn derive_from_request(item: TokenStream) -> TokenStream {
|
|||||||
/// ```
|
/// ```
|
||||||
/// use axum_macros::FromRequestParts;
|
/// use axum_macros::FromRequestParts;
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// extract::{Query, TypedHeader},
|
/// extract::Query,
|
||||||
|
/// };
|
||||||
|
/// use axum_extra::{
|
||||||
|
/// TypedHeader,
|
||||||
/// headers::ContentType,
|
/// headers::ContentType,
|
||||||
/// };
|
/// };
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
|
@ -15,7 +15,7 @@ error[E0277]: the trait bound `bool: FromRequestParts<()>` is not satisfied
|
|||||||
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
||||||
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
||||||
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
||||||
and 26 others
|
and $N others
|
||||||
= note: required for `bool` to implement `FromRequest<(), axum_core::extract::private::ViaParts>`
|
= 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`
|
note: required by a bound in `__axum_macros_check_handler_0_from_request_check`
|
||||||
--> tests/debug_handler/fail/argument_not_extractor.rs:4:23
|
--> tests/debug_handler/fail/argument_not_extractor.rs:4:23
|
||||||
|
@ -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
|
--> tests/debug_handler/fail/wrong_return_type.rs:4:23
|
||||||
|
|
|
|
||||||
4 | async fn handler() -> bool {
|
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`
|
||||||
|
@ -20,5 +20,5 @@ note: required by a bound in `axum::routing::get`
|
|||||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||||
|
|
|
|
||||||
| top_level_handler_fn!(get, GET);
|
| 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)
|
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
@ -20,5 +20,5 @@ note: required by a bound in `axum::routing::get`
|
|||||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||||
|
|
|
|
||||||
| top_level_handler_fn!(get, GET);
|
| 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)
|
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
@ -5,39 +5,39 @@ error: cannot use `rejection` without `via`
|
|||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
||||||
error[E0277]: the trait bound `fn(MyExtractor) -> impl Future<Output = ()> {handler}: Handler<_, _>` is not satisfied
|
error[E0277]: the trait bound `fn(MyExtractor) -> impl Future<Output = ()> {handler}: Handler<_, _>` is not satisfied
|
||||||
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:50
|
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:50
|
||||||
|
|
|
|
||||||
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
|
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
|
||||||
| --- ^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(MyExtractor) -> impl Future<Output = ()> {handler}`
|
| --- ^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(MyExtractor) -> impl Future<Output = ()> {handler}`
|
||||||
| |
|
| |
|
||||||
| required by a bound introduced by this call
|
| required by a bound introduced by this call
|
||||||
|
|
|
|
||||||
= note: Consider using `#[axum::debug_handler]` to improve the error message
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
|
||||||
= help: the following other types implement trait `Handler<T, S>`:
|
= help: the following other types implement trait `Handler<T, S>`:
|
||||||
<Layered<L, H, T, S> as Handler<T, S>>
|
<Layered<L, H, T, S> as Handler<T, S>>
|
||||||
<MethodRouter<S> as Handler<(), S>>
|
<MethodRouter<S> as Handler<(), S>>
|
||||||
note: required by a bound in `axum::routing::get`
|
note: required by a bound in `axum::routing::get`
|
||||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||||
|
|
|
|
||||||
| top_level_handler_fn!(get, GET);
|
| 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)
|
= 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<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}: Handler<_, _>` is not satisfied
|
error[E0277]: the trait bound `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}: Handler<_, _>` is not satisfied
|
||||||
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:64
|
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:64
|
||||||
|
|
|
|
||||||
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
|
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
|
||||||
| ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}`
|
| ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for fn item `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}`
|
||||||
| |
|
| |
|
||||||
| required by a bound introduced by this call
|
| required by a bound introduced by this call
|
||||||
|
|
|
|
||||||
= note: Consider using `#[axum::debug_handler]` to improve the error message
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
|
||||||
= help: the following other types implement trait `Handler<T, S>`:
|
= help: the following other types implement trait `Handler<T, S>`:
|
||||||
<Layered<L, H, T, S> as Handler<T, S>>
|
<Layered<L, H, T, S> as Handler<T, S>>
|
||||||
<MethodRouter<S> as Handler<(), S>>
|
<MethodRouter<S> as Handler<(), S>>
|
||||||
note: required by a bound in `MethodRouter::<S>::post`
|
note: required by a bound in `MethodRouter::<S>::post`
|
||||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||||
|
|
|
|
||||||
| chained_handler_fn!(post, POST);
|
| chained_handler_fn!(post, POST);
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<S>::post`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<S>::post`
|
||||||
= note: this error originates in the macro `chained_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the macro `chained_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
@ -15,4 +15,4 @@ error[E0277]: the trait bound `String: FromRequestParts<S>` is not satisfied
|
|||||||
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
||||||
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
||||||
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
||||||
and 27 others
|
and $N others
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{FromRequest, TypedHeader, rejection::TypedHeaderRejection},
|
extract::FromRequest,
|
||||||
response::Response,
|
response::Response,
|
||||||
|
};
|
||||||
|
use axum_extra::{
|
||||||
|
TypedHeader,
|
||||||
|
typed_header::TypedHeaderRejection,
|
||||||
headers::{self, UserAgent},
|
headers::{self, UserAgent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{rejection::TypedHeaderRejection, FromRequestParts, TypedHeader},
|
extract::FromRequestParts,
|
||||||
headers::{self, UserAgent},
|
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
|
use axum_extra::{
|
||||||
|
TypedHeader,
|
||||||
|
typed_header::TypedHeaderRejection,
|
||||||
|
headers::{self, UserAgent},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromRequestParts)]
|
#[derive(FromRequestParts)]
|
||||||
struct Extractor {
|
struct Extractor {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
response::Response,
|
response::Response,
|
||||||
extract::{
|
extract::{Extension, FromRequest},
|
||||||
rejection::TypedHeaderRejection,
|
};
|
||||||
Extension, FromRequest, TypedHeader,
|
use axum_extra::{
|
||||||
},
|
TypedHeader,
|
||||||
|
typed_header::TypedHeaderRejection,
|
||||||
headers::{self, UserAgent},
|
headers::{self, UserAgent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
response::Response,
|
response::Response,
|
||||||
extract::{
|
extract::{Extension, FromRequestParts},
|
||||||
rejection::TypedHeaderRejection,
|
};
|
||||||
Extension, FromRequestParts, TypedHeader,
|
use axum_extra::{
|
||||||
},
|
TypedHeader,
|
||||||
|
typed_header::TypedHeaderRejection,
|
||||||
headers::{self, UserAgent},
|
headers::{self, UserAgent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ use axum::{
|
|||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
body::Body,
|
|
||||||
Extension, Router,
|
Extension, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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:** Change `sse::Event::json_data` to use `axum_core::Error` as its error type ([#1762])
|
||||||
- **breaking:** Rename `DefaultOnFailedUpdgrade` to `DefaultOnFailedUpgrade` ([#1664])
|
- **breaking:** Rename `DefaultOnFailedUpdgrade` to `DefaultOnFailedUpgrade` ([#1664])
|
||||||
- **breaking:** Rename `OnFailedUpdgrade` to `OnFailedUpgrade` ([#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
|
- **breaking:** Removed re-exports of `Empty` and `Full`. Use
|
||||||
`axum::body::Body::empty` and `axum::body::Body::from` respectively ([#1789])
|
`axum::body::Body::empty` and `axum::body::Body::from` respectively ([#1789])
|
||||||
- **breaking:** The response returned by `IntoResponse::into_response` must use
|
- **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
|
[#1664]: https://github.com/tokio-rs/axum/pull/1664
|
||||||
[#1751]: https://github.com/tokio-rs/axum/pull/1751
|
[#1751]: https://github.com/tokio-rs/axum/pull/1751
|
||||||
[#1762]: https://github.com/tokio-rs/axum/pull/1762
|
[#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
|
[#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
|
[#1868]: https://github.com/tokio-rs/axum/pull/1868
|
||||||
|
|
||||||
# 0.6.16 (18. April, 2023)
|
# 0.6.16 (18. April, 2023)
|
||||||
|
@ -58,7 +58,6 @@ tower-hyper-http-body-compat = { version = "0.1.4", features = ["server", "http1
|
|||||||
# optional dependencies
|
# optional dependencies
|
||||||
axum-macros = { path = "../axum-macros", version = "0.3.7", optional = true }
|
axum-macros = { path = "../axum-macros", version = "0.3.7", optional = true }
|
||||||
base64 = { version = "0.21.0", optional = true }
|
base64 = { version = "0.21.0", optional = true }
|
||||||
headers = { version = "0.3.7", optional = true }
|
|
||||||
multer = { version = "2.0.0", optional = true }
|
multer = { version = "2.0.0", optional = true }
|
||||||
serde_json = { version = "1.0", features = ["raw_value"], optional = true }
|
serde_json = { version = "1.0", features = ["raw_value"], optional = true }
|
||||||
serde_path_to_error = { version = "0.1.8", optional = true }
|
serde_path_to_error = { version = "0.1.8", optional = true }
|
||||||
@ -190,8 +189,6 @@ allowed = [
|
|||||||
"futures_core",
|
"futures_core",
|
||||||
"futures_sink",
|
"futures_sink",
|
||||||
"futures_util",
|
"futures_util",
|
||||||
"headers",
|
|
||||||
"headers_core",
|
|
||||||
"http",
|
"http",
|
||||||
"http_body",
|
"http_body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
@ -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)
|
- [Accessing other extractors in `FromRequest` or `FromRequestParts` implementations](#accessing-other-extractors-in-fromrequest-or-fromrequestparts-implementations)
|
||||||
- [Request body limits](#request-body-limits)
|
- [Request body limits](#request-body-limits)
|
||||||
- [Request body extractors](#request-body-extractors)
|
- [Request body extractors](#request-body-extractors)
|
||||||
- [Running extractors from middleware](#running-extractors-from-middleware)
|
|
||||||
- [Wrapping extractors](#wrapping-extractors)
|
- [Wrapping extractors](#wrapping-extractors)
|
||||||
- [Logging rejections](#logging-rejections)
|
- [Logging rejections](#logging-rejections)
|
||||||
|
|
||||||
@ -56,9 +55,8 @@ Some commonly used extractors are:
|
|||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Request, Json, TypedHeader, Path, Extension, Query},
|
extract::{Request, Json, Path, Extension, Query},
|
||||||
routing::post,
|
routing::post,
|
||||||
headers::UserAgent,
|
|
||||||
http::header::HeaderMap,
|
http::header::HeaderMap,
|
||||||
body::{Bytes, Body},
|
body::{Bytes, Body},
|
||||||
Router,
|
Router,
|
||||||
@ -76,10 +74,6 @@ async fn query(Query(params): Query<HashMap<String, String>>) {}
|
|||||||
// `HeaderMap` gives you all the headers
|
// `HeaderMap` gives you all the headers
|
||||||
async fn headers(headers: HeaderMap) {}
|
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<UserAgent>) {}
|
|
||||||
|
|
||||||
// `String` consumes the request body and ensures it is valid utf-8
|
// `String` consumes the request body and ensures it is valid utf-8
|
||||||
async fn string(body: String) {}
|
async fn string(body: String) {}
|
||||||
|
|
||||||
@ -102,8 +96,6 @@ struct State { /* ... */ }
|
|||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/path/:user_id", post(path))
|
.route("/path/:user_id", post(path))
|
||||||
.route("/query", post(query))
|
.route("/query", post(query))
|
||||||
.route("/user_agent", post(user_agent))
|
|
||||||
.route("/headers", post(headers))
|
|
||||||
.route("/string", post(string))
|
.route("/string", post(string))
|
||||||
.route("/bytes", post(bytes))
|
.route("/bytes", post(bytes))
|
||||||
.route("/json", post(json))
|
.route("/json", post(json))
|
||||||
@ -562,9 +554,8 @@ in your implementation.
|
|||||||
```rust
|
```rust
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{Extension, FromRequestParts, TypedHeader},
|
extract::{Extension, FromRequestParts},
|
||||||
headers::{authorization::Bearer, Authorization},
|
http::{StatusCode, HeaderMap, request::Parts},
|
||||||
http::{StatusCode, request::Parts},
|
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
@ -588,10 +579,9 @@ where
|
|||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
// You can either call them directly...
|
// You can either call them directly...
|
||||||
let TypedHeader(Authorization(token)) =
|
let headers = HeaderMap::from_request_parts(parts, state)
|
||||||
TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state)
|
.await
|
||||||
.await
|
.map_err(|err| match err {})?;
|
||||||
.map_err(|err| err.into_response())?;
|
|
||||||
|
|
||||||
// ... or use `extract` / `extract_with_state` from `RequestExt` / `RequestPartsExt`
|
// ... or use `extract` / `extract_with_state` from `RequestExt` / `RequestPartsExt`
|
||||||
use axum::RequestPartsExt;
|
use axum::RequestPartsExt;
|
||||||
@ -621,51 +611,6 @@ For security reasons, [`Bytes`] will, by default, not accept bodies larger than
|
|||||||
|
|
||||||
For more details, including how to disable this limit, see [`DefaultBodyLimit`].
|
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<Response, StatusCode> {
|
|
||||||
// running extractors requires a `axum::http::request::Parts`
|
|
||||||
let (mut parts, body) = request.into_parts();
|
|
||||||
|
|
||||||
// `TypedHeader<Authorization<Bearer>>` extracts the auth token
|
|
||||||
let auth: TypedHeader<Authorization<Bearer>> = 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
|
# Wrapping extractors
|
||||||
|
|
||||||
If you want write an extractor that generically wraps another extractor (that
|
If you want write an extractor that generically wraps another extractor (that
|
||||||
|
@ -18,9 +18,7 @@ let app = Router::new()
|
|||||||
async fn fallback(uri: Uri) -> (StatusCode, String) {
|
async fn fallback(uri: Uri) -> (StatusCode, String) {
|
||||||
(StatusCode::NOT_FOUND, format!("No route for {}", uri))
|
(StatusCode::NOT_FOUND, format!("No route for {}", uri))
|
||||||
}
|
}
|
||||||
# async {
|
# let _: Router = app;
|
||||||
# hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
|
||||||
# };
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Fallbacks only apply to routes that aren't matched by anything in the
|
Fallbacks only apply to routes that aren't matched by anything in the
|
||||||
@ -40,10 +38,8 @@ async fn handler() {}
|
|||||||
let app = Router::new().fallback(handler);
|
let app = Router::new().fallback(handler);
|
||||||
|
|
||||||
# async {
|
# async {
|
||||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
.serve(app.into_make_service())
|
axum::serve(listener, app).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
# };
|
# };
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -55,9 +51,7 @@ use axum::handler::HandlerWithoutStateExt;
|
|||||||
async fn handler() {}
|
async fn handler() {}
|
||||||
|
|
||||||
# async {
|
# async {
|
||||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
.serve(handler.into_make_service())
|
axum::serve(listener, handler.into_make_service()).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
# };
|
# };
|
||||||
```
|
```
|
||||||
|
@ -76,10 +76,6 @@ pub use self::request_parts::OriginalUri;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::ws::WebSocketUpgrade;
|
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`
|
// this is duplicated in `axum-extra/src/extract/form.rs`
|
||||||
pub(super) fn has_content_type(headers: &HeaderMap, expected_content_type: &mime::Mime) -> bool {
|
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) {
|
let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
|
||||||
|
@ -207,6 +207,3 @@ composite_rejection! {
|
|||||||
MatchedPathMissing,
|
MatchedPathMissing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "headers")]
|
|
||||||
pub use crate::typed_header::{TypedHeaderRejection, TypedHeaderRejectionReason};
|
|
||||||
|
@ -336,7 +336,6 @@
|
|||||||
//!
|
//!
|
||||||
//! Name | Description | Default?
|
//! Name | Description | Default?
|
||||||
//! ---|---|---
|
//! ---|---|---
|
||||||
//! `headers` | Enables extracting typed headers via [`TypedHeader`] | No
|
|
||||||
//! `http1` | Enables hyper's `http1` feature | Yes
|
//! `http1` | Enables hyper's `http1` feature | Yes
|
||||||
//! `http2` | Enables hyper's `http2` feature | No
|
//! `http2` | Enables hyper's `http2` feature | No
|
||||||
//! `json` | Enables the [`Json`] type and some similar convenience functionality | Yes
|
//! `json` | Enables the [`Json`] type and some similar convenience functionality | Yes
|
||||||
@ -351,7 +350,6 @@
|
|||||||
//! `form` | Enables the `Form` extractor | Yes
|
//! `form` | Enables the `Form` extractor | Yes
|
||||||
//! `query` | Enables the `Query` extractor | Yes
|
//! `query` | Enables the `Query` extractor | Yes
|
||||||
//!
|
//!
|
||||||
//! [`TypedHeader`]: crate::extract::TypedHeader
|
|
||||||
//! [`MatchedPath`]: crate::extract::MatchedPath
|
//! [`MatchedPath`]: crate::extract::MatchedPath
|
||||||
//! [`Multipart`]: crate::extract::Multipart
|
//! [`Multipart`]: crate::extract::Multipart
|
||||||
//! [`OriginalUri`]: crate::extract::OriginalUri
|
//! [`OriginalUri`]: crate::extract::OriginalUri
|
||||||
@ -435,8 +433,6 @@ mod form;
|
|||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
mod json;
|
mod json;
|
||||||
mod service_ext;
|
mod service_ext;
|
||||||
#[cfg(feature = "headers")]
|
|
||||||
mod typed_header;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
@ -454,9 +450,6 @@ mod test_helpers;
|
|||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use async_trait::async_trait;
|
pub use async_trait::async_trait;
|
||||||
#[cfg(feature = "headers")]
|
|
||||||
#[doc(no_inline)]
|
|
||||||
pub use headers;
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use http;
|
pub use http;
|
||||||
|
|
||||||
@ -468,10 +461,6 @@ pub use self::json::Json;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::routing::Router;
|
pub use self::routing::Router;
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
#[cfg(feature = "headers")]
|
|
||||||
pub use self::typed_header::TypedHeader;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
#[cfg(feature = "form")]
|
#[cfg(feature = "form")]
|
||||||
pub use self::form::Form;
|
pub use self::form::Form;
|
||||||
|
@ -61,31 +61,38 @@ use tower_service::Service;
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// use axum::{
|
/// use axum::{
|
||||||
/// Router,
|
/// Router,
|
||||||
/// extract::{Request, TypedHeader},
|
/// extract::Request,
|
||||||
/// http::StatusCode,
|
/// http::{StatusCode, HeaderMap},
|
||||||
/// headers::authorization::{Authorization, Bearer},
|
|
||||||
/// middleware::{self, Next},
|
/// middleware::{self, Next},
|
||||||
/// response::Response,
|
/// response::Response,
|
||||||
/// routing::get,
|
/// routing::get,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// async fn auth(
|
/// async fn auth(
|
||||||
/// // run the `TypedHeader` extractor
|
/// // run the `HeaderMap` extractor
|
||||||
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
/// headers: HeaderMap,
|
||||||
/// // you can also add more extractors here but the last
|
/// // you can also add more extractors here but the last
|
||||||
/// // extractor must implement `FromRequest` which
|
/// // extractor must implement `FromRequest` which
|
||||||
/// // `Request` does
|
/// // `Request` does
|
||||||
/// request: Request,
|
/// request: Request,
|
||||||
/// next: Next,
|
/// next: Next,
|
||||||
/// ) -> Result<Response, StatusCode> {
|
/// ) -> Result<Response, StatusCode> {
|
||||||
/// if token_is_valid(auth.token()) {
|
/// match get_token(&headers) {
|
||||||
/// let response = next.run(request).await;
|
/// Some(token) if token_is_valid(token) => {
|
||||||
/// Ok(response)
|
/// let response = next.run(request).await;
|
||||||
/// } else {
|
/// Ok(response)
|
||||||
/// Err(StatusCode::UNAUTHORIZED)
|
/// }
|
||||||
|
/// _ => {
|
||||||
|
/// Err(StatusCode::UNAUTHORIZED)
|
||||||
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
/// fn get_token(headers: &HeaderMap) -> Option<&str> {
|
||||||
|
/// // ...
|
||||||
|
/// # None
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// fn token_is_valid(token: &str) -> bool {
|
/// fn token_is_valid(token: &str) -> bool {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// # false
|
/// # false
|
||||||
|
@ -12,10 +12,6 @@ pub mod sse;
|
|||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
pub use crate::Json;
|
pub use crate::Json;
|
||||||
|
|
||||||
#[doc(no_inline)]
|
|
||||||
#[cfg(feature = "headers")]
|
|
||||||
pub use crate::TypedHeader;
|
|
||||||
|
|
||||||
#[cfg(feature = "form")]
|
#[cfg(feature = "form")]
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::form::Form;
|
pub use crate::form::Form;
|
||||||
|
@ -627,10 +627,10 @@ where
|
|||||||
fn layer<L>(self, layer: L) -> Endpoint<S>
|
fn layer<L>(self, layer: L) -> Endpoint<S>
|
||||||
where
|
where
|
||||||
L: Layer<Route> + Clone + Send + 'static,
|
L: Layer<Route> + Clone + Send + 'static,
|
||||||
L::Service: Service<Request> + Clone + Send + 'static,
|
L::Service: Service<Request<Body>> + Clone + Send + 'static,
|
||||||
<L::Service as Service<Request>>::Response: IntoResponse + 'static,
|
<L::Service as Service<Request<Body>>>::Response: IntoResponse + 'static,
|
||||||
<L::Service as Service<Request>>::Error: Into<Infallible> + 'static,
|
<L::Service as Service<Request<Body>>>::Error: Into<Infallible> + 'static,
|
||||||
<L::Service as Service<Request>>::Future: Send + 'static,
|
<L::Service as Service<Request<Body>>>::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Endpoint::MethodRouter(method_router) => {
|
Endpoint::MethodRouter(method_router) => {
|
||||||
|
@ -77,10 +77,8 @@ async fn main() {
|
|||||||
// run it with hyper
|
// run it with hyper
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
tracing::debug!("listening on {}", addr);
|
tracing::debug!("listening on {}", addr);
|
||||||
axum::Server::bind(&addr)
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
.serve(app.into_make_service())
|
axum::serve(listener, app).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_user(
|
async fn create_user(
|
||||||
|
@ -85,10 +85,8 @@ async fn main() {
|
|||||||
// run it with hyper
|
// run it with hyper
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
tracing::debug!("listening on {}", addr);
|
tracing::debug!("listening on {}", addr);
|
||||||
axum::Server::bind(&addr)
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
.serve(app.into_make_service())
|
axum::serve(listener, app).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_user(
|
async fn create_user(
|
||||||
|
@ -5,8 +5,8 @@ edition = "2021"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["headers"] }
|
axum = { path = "../../axum" }
|
||||||
headers = "0.3"
|
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
||||||
jsonwebtoken = "8.0"
|
jsonwebtoken = "8.0"
|
||||||
once_cell = "1.8"
|
once_cell = "1.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
@ -8,13 +8,16 @@
|
|||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{FromRequestParts, TypedHeader},
|
extract::FromRequestParts,
|
||||||
headers::{authorization::Bearer, Authorization},
|
|
||||||
http::{request::Parts, StatusCode},
|
http::{request::Parts, StatusCode},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, RequestPartsExt, Router,
|
Json, RequestPartsExt, Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::{
|
||||||
|
headers::{authorization::Bearer, Authorization},
|
||||||
|
TypedHeader,
|
||||||
|
};
|
||||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -6,8 +6,8 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-session = "3.0.0"
|
async-session = "3.0.0"
|
||||||
axum = { path = "../../axum", features = ["headers"] }
|
axum = { path = "../../axum" }
|
||||||
headers = "0.3"
|
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
oauth2 = "4.1"
|
oauth2 = "4.1"
|
||||||
# Use Rustls because it makes it easier to cross-compile on CI
|
# Use Rustls because it makes it easier to cross-compile on CI
|
||||||
|
@ -11,14 +11,13 @@
|
|||||||
use async_session::{MemoryStore, Session, SessionStore};
|
use async_session::{MemoryStore, Session, SessionStore};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{
|
extract::{FromRef, FromRequestParts, Query, State},
|
||||||
rejection::TypedHeaderRejectionReason, FromRef, FromRequestParts, Query, State, TypedHeader,
|
|
||||||
},
|
|
||||||
http::{header::SET_COOKIE, HeaderMap},
|
http::{header::SET_COOKIE, HeaderMap},
|
||||||
response::{IntoResponse, Redirect, Response},
|
response::{IntoResponse, Redirect, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
RequestPartsExt, Router,
|
RequestPartsExt, Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::{headers, typed_header::TypedHeaderRejectionReason, TypedHeader};
|
||||||
use http::{header, request::Parts};
|
use http::{header, request::Parts};
|
||||||
use oauth2::{
|
use oauth2::{
|
||||||
basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId,
|
basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId,
|
||||||
|
@ -6,7 +6,8 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-session = "3.0.0"
|
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"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
use async_session::{MemoryStore, Session, SessionStore as _};
|
use async_session::{MemoryStore, Session, SessionStore as _};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{FromRef, FromRequestParts, TypedHeader},
|
extract::{FromRef, FromRequestParts},
|
||||||
headers::Cookie,
|
|
||||||
http::{
|
http::{
|
||||||
self,
|
self,
|
||||||
header::{HeaderMap, HeaderValue},
|
header::{HeaderMap, HeaderValue},
|
||||||
@ -19,6 +18,7 @@ use axum::{
|
|||||||
routing::get,
|
routing::get,
|
||||||
RequestPartsExt, Router,
|
RequestPartsExt, Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::{headers::Cookie, TypedHeader};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
@ -5,7 +5,8 @@ edition = "2021"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["headers"] }
|
axum = { path = "../../axum" }
|
||||||
|
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
headers = "0.3"
|
headers = "0.3"
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::TypedHeader,
|
|
||||||
response::sse::{Event, Sse},
|
response::sse::{Event, Sse},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::{headers, TypedHeader};
|
||||||
use futures::stream::{self, Stream};
|
use futures::stream::{self, Stream};
|
||||||
use std::{convert::Infallible, path::PathBuf, time::Duration};
|
use std::{convert::Infallible, path::PathBuf, time::Duration};
|
||||||
use tokio_stream::StreamExt as _;
|
use tokio_stream::StreamExt as _;
|
||||||
|
@ -5,7 +5,8 @@ edition = "2021"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { path = "../../axum", features = ["ws", "headers"] }
|
axum = { path = "../../axum", features = ["ws"] }
|
||||||
|
axum-extra = { path = "../../axum-extra", features = ["typed-header"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||||
headers = "0.3"
|
headers = "0.3"
|
||||||
|
@ -17,14 +17,12 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{
|
extract::ws::{Message, WebSocket, WebSocketUpgrade},
|
||||||
ws::{Message, WebSocket, WebSocketUpgrade},
|
|
||||||
TypedHeader,
|
|
||||||
},
|
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::TypedHeader;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user