Misc documentation improvements (#1647)

Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
This commit is contained in:
David Pedersen 2023-01-07 14:07:54 +01:00 committed by GitHub
parent 211de92d24
commit 1aa357c879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 368 additions and 51 deletions

View File

@ -25,7 +25,7 @@ tower-service = "0.3"
rustversion = "1.0.9" rustversion = "1.0.9"
[dev-dependencies] [dev-dependencies]
axum = { path = "../axum", version = "0.6.0" } axum = { path = "../axum", version = "0.6.0", features = ["headers"] }
futures-util = "0.3" futures-util = "0.3"
hyper = "0.14" hyper = "0.14"
tokio = { version = "1.0", features = ["macros"] } tokio = { version = "1.0", features = ["macros"] }

View File

@ -16,6 +16,58 @@ pub trait RequestExt<B>: sealed::Sealed<B> + Sized {
/// ///
/// Note this consumes the request. Use [`RequestExt::extract_parts`] if you're not extracting /// Note this consumes the request. Use [`RequestExt::extract_parts`] if you're not extracting
/// the body and don't want to consume the request. /// the body and don't want to consume the request.
///
/// # Example
///
/// ```
/// use axum::{
/// async_trait,
/// extract::FromRequest,
/// http::{header::CONTENT_TYPE, Request, StatusCode},
/// response::{IntoResponse, Response},
/// Form, Json, RequestExt,
/// };
///
/// struct FormOrJson<T>(T);
///
/// #[async_trait]
/// impl<S, B, T> FromRequest<S, B> for FormOrJson<T>
/// where
/// Json<T>: FromRequest<(), B>,
/// Form<T>: FromRequest<(), B>,
/// T: 'static,
/// B: Send + 'static,
/// S: Send + Sync,
/// {
/// type Rejection = Response;
///
/// async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
/// let content_type = req
/// .headers()
/// .get(CONTENT_TYPE)
/// .and_then(|value| value.to_str().ok())
/// .ok_or_else(|| StatusCode::BAD_REQUEST.into_response())?;
///
/// if content_type.starts_with("application/json") {
/// let Json(payload) = req
/// .extract::<Json<T>, _>()
/// .await
/// .map_err(|err| err.into_response())?;
///
/// Ok(Self(payload))
/// } else if content_type.starts_with("application/x-www-form-urlencoded") {
/// let Form(payload) = req
/// .extract::<Form<T>, _>()
/// .await
/// .map_err(|err| err.into_response())?;
///
/// Ok(Self(payload))
/// } else {
/// Err(StatusCode::BAD_REQUEST.into_response())
/// }
/// }
/// }
/// ```
fn extract<E, M>(self) -> BoxFuture<'static, Result<E, E::Rejection>> fn extract<E, M>(self) -> BoxFuture<'static, Result<E, E::Rejection>>
where where
E: FromRequest<(), B, M> + 'static, E: FromRequest<(), B, M> + 'static,
@ -27,6 +79,54 @@ pub trait RequestExt<B>: sealed::Sealed<B> + Sized {
/// ///
/// Note this consumes the request. Use [`RequestExt::extract_parts_with_state`] if you're not /// Note this consumes the request. Use [`RequestExt::extract_parts_with_state`] if you're not
/// extracting the body and don't want to consume the request. /// extracting the body and don't want to consume the request.
///
/// # Example
///
/// ```
/// use axum::{
/// async_trait,
/// extract::{FromRef, FromRequest},
/// http::Request,
/// RequestExt,
/// };
///
/// struct MyExtractor {
/// requires_state: RequiresState,
/// }
///
/// #[async_trait]
/// impl<S, B> FromRequest<S, B> for MyExtractor
/// where
/// String: FromRef<S>,
/// S: Send + Sync,
/// B: Send + 'static,
/// {
/// type Rejection = std::convert::Infallible;
///
/// async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
/// let requires_state = req.extract_with_state::<RequiresState, _, _>(state).await?;
///
/// Ok(Self { requires_state })
/// }
/// }
///
/// // some extractor that consumes the request body and requires state
/// struct RequiresState { /* ... */ }
///
/// #[async_trait]
/// impl<S, B> FromRequest<S, B> for RequiresState
/// where
/// String: FromRef<S>,
/// S: Send + Sync,
/// B: Send + 'static,
/// {
/// // ...
/// # type Rejection = std::convert::Infallible;
/// # async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
/// # todo!()
/// # }
/// }
/// ```
fn extract_with_state<E, S, M>(self, state: &S) -> BoxFuture<'_, Result<E, E::Rejection>> fn extract_with_state<E, S, M>(self, state: &S) -> BoxFuture<'_, Result<E, E::Rejection>>
where where
E: FromRequest<S, B, M> + 'static, E: FromRequest<S, B, M> + 'static,
@ -35,6 +135,52 @@ pub trait RequestExt<B>: sealed::Sealed<B> + Sized {
/// Apply a parts extractor to this `Request`. /// Apply a parts extractor to this `Request`.
/// ///
/// This is just a convenience for `E::from_request_parts(parts, state)`. /// This is just a convenience for `E::from_request_parts(parts, state)`.
///
/// # Example
///
/// ```
/// use axum::{
/// async_trait,
/// extract::FromRequest,
/// headers::{authorization::Bearer, Authorization},
/// http::Request,
/// response::{IntoResponse, Response},
/// Json, RequestExt, TypedHeader,
/// };
///
/// struct MyExtractor<T> {
/// bearer_token: String,
/// payload: T,
/// }
///
/// #[async_trait]
/// impl<S, B, T> FromRequest<S, B> for MyExtractor<T>
/// where
/// B: Send + 'static,
/// S: Send + Sync,
/// Json<T>: FromRequest<(), B>,
/// T: 'static,
/// {
/// type Rejection = Response;
///
/// async fn from_request(mut req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
/// let TypedHeader(auth_header) = req
/// .extract_parts::<TypedHeader<Authorization<Bearer>>>()
/// .await
/// .map_err(|err| err.into_response())?;
///
/// let Json(payload) = req
/// .extract::<Json<T>, _>()
/// .await
/// .map_err(|err| err.into_response())?;
///
/// Ok(Self {
/// bearer_token: auth_header.token().to_owned(),
/// payload,
/// })
/// }
/// }
/// ```
fn extract_parts<E>(&mut self) -> BoxFuture<'_, Result<E, E::Rejection>> fn extract_parts<E>(&mut self) -> BoxFuture<'_, Result<E, E::Rejection>>
where where
E: FromRequestParts<()> + 'static; E: FromRequestParts<()> + 'static;
@ -42,6 +188,67 @@ pub trait RequestExt<B>: sealed::Sealed<B> + Sized {
/// Apply a parts extractor that requires some state to this `Request`. /// Apply a parts extractor that requires some state to this `Request`.
/// ///
/// This is just a convenience for `E::from_request_parts(parts, state)`. /// This is just a convenience for `E::from_request_parts(parts, state)`.
///
/// # Example
///
/// ```
/// use axum::{
/// async_trait,
/// extract::{FromRef, FromRequest, FromRequestParts},
/// http::{request::Parts, Request},
/// response::{IntoResponse, Response},
/// Json, RequestExt,
/// };
///
/// struct MyExtractor<T> {
/// requires_state: RequiresState,
/// payload: T,
/// }
///
/// #[async_trait]
/// impl<S, B, T> FromRequest<S, B> for MyExtractor<T>
/// where
/// String: FromRef<S>,
/// Json<T>: FromRequest<(), B>,
/// T: 'static,
/// S: Send + Sync,
/// B: Send + 'static,
/// {
/// type Rejection = Response;
///
/// async fn from_request(mut req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
/// let requires_state = req
/// .extract_parts_with_state::<RequiresState, _>(state)
/// .await
/// .map_err(|err| err.into_response())?;
///
/// let Json(payload) = req
/// .extract::<Json<T>, _>()
/// .await
/// .map_err(|err| err.into_response())?;
///
/// Ok(Self {
/// requires_state,
/// payload,
/// })
/// }
/// }
///
/// struct RequiresState {}
///
/// #[async_trait]
/// impl<S> FromRequestParts<S> for RequiresState
/// where
/// String: FromRef<S>,
/// S: Send + Sync,
/// {
/// // ...
/// # type Rejection = std::convert::Infallible;
/// # async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
/// # todo!()
/// # }
/// }
/// ```
fn extract_parts_with_state<'a, E, S>( fn extract_parts_with_state<'a, E, S>(
&'a mut self, &'a mut self,
state: &'a S, state: &'a S,

View File

@ -12,6 +12,49 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
/// Apply an extractor to this `Parts`. /// Apply an extractor to this `Parts`.
/// ///
/// This is just a convenience for `E::from_request_parts(parts, &())`. /// This is just a convenience for `E::from_request_parts(parts, &())`.
///
/// # Example
///
/// ```
/// use axum::{
/// extract::{Query, TypedHeader, FromRequestParts},
/// response::{Response, IntoResponse},
/// headers::UserAgent,
/// http::request::Parts,
/// RequestPartsExt,
/// async_trait,
/// };
/// use std::collections::HashMap;
///
/// struct MyExtractor {
/// user_agent: String,
/// query_params: HashMap<String, String>,
/// }
///
/// #[async_trait]
/// impl<S> FromRequestParts<S> for MyExtractor
/// where
/// S: Send + Sync,
/// {
/// type Rejection = Response;
///
/// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
/// let user_agent = parts
/// .extract::<TypedHeader<UserAgent>>()
/// .await
/// .map(|user_agent| user_agent.as_str().to_owned())
/// .map_err(|err| err.into_response())?;
///
/// let query_params = parts
/// .extract::<Query<HashMap<String, String>>>()
/// .await
/// .map(|Query(params)| params)
/// .map_err(|err| err.into_response())?;
///
/// Ok(MyExtractor { user_agent, query_params })
/// }
/// }
/// ```
fn extract<E>(&mut self) -> BoxFuture<'_, Result<E, E::Rejection>> fn extract<E>(&mut self) -> BoxFuture<'_, Result<E, E::Rejection>>
where where
E: FromRequestParts<()> + 'static; E: FromRequestParts<()> + 'static;
@ -19,6 +62,55 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
/// Apply an extractor that requires some state to this `Parts`. /// Apply an extractor that requires some state to this `Parts`.
/// ///
/// This is just a convenience for `E::from_request_parts(parts, state)`. /// This is just a convenience for `E::from_request_parts(parts, state)`.
///
/// # Example
///
/// ```
/// use axum::{
/// extract::{FromRef, FromRequestParts},
/// response::{Response, IntoResponse},
/// http::request::Parts,
/// RequestPartsExt,
/// async_trait,
/// };
///
/// struct MyExtractor {
/// requires_state: RequiresState,
/// }
///
/// #[async_trait]
/// impl<S> FromRequestParts<S> for MyExtractor
/// where
/// String: FromRef<S>,
/// S: Send + Sync,
/// {
/// type Rejection = std::convert::Infallible;
///
/// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
/// let requires_state = parts
/// .extract_with_state::<RequiresState, _>(state)
/// .await?;
///
/// Ok(MyExtractor { requires_state })
/// }
/// }
///
/// struct RequiresState { /* ... */ }
///
/// // some extractor that requires a `String` in the state
/// #[async_trait]
/// impl<S> FromRequestParts<S> for RequiresState
/// where
/// String: FromRef<S>,
/// S: Send + Sync,
/// {
/// // ...
/// # type Rejection = std::convert::Infallible;
/// # async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
/// # unimplemented!()
/// # }
/// }
/// ```
fn extract_with_state<'a, E, S>( fn extract_with_state<'a, E, S>(
&'a mut self, &'a mut self,
state: &'a S, state: &'a S,

View File

@ -98,6 +98,13 @@ axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
# }; # };
``` ```
# State is global within the router
The state passed to this method will be used for all requests this router
receives. That means it is not suitable for holding state derived from a
request, such as authorization data extracted in a middleware. Use [`Extension`]
instead for such data.
# What `S` in `Router<S>` means # What `S` in `Router<S>` means
`Router<S>` means a router that is _missing_ a state of type `S` to be able to `Router<S>` means a router that is _missing_ a state of type `S` to be able to
@ -234,3 +241,5 @@ which may impact performance and reduce allocations.
Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`] Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
do this automatically. do this automatically.
[`Extension`]: crate::Extension

View File

@ -24,27 +24,66 @@ use tower_service::Service;
/// 3. Take [`Next<B>`](Next) as the final argument. /// 3. Take [`Next<B>`](Next) as the final argument.
/// 4. Return something that implements [`IntoResponse`]. /// 4. Return something that implements [`IntoResponse`].
/// ///
/// Note that this function doesn't support extracting [`State`]. For that, use [`from_fn_with_state`].
///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use axum::{ /// use axum::{
/// Router, /// Router,
/// http::{self, Request, StatusCode}, /// http::{self, Request},
/// routing::get, /// routing::get,
/// response::{IntoResponse, Response}, /// response::Response,
/// middleware::{self, Next}, /// middleware::{self, Next},
/// }; /// };
/// ///
/// async fn auth<B>(req: Request<B>, next: Next<B>) -> Result<Response, StatusCode> { /// async fn my_middleware<B>(
/// let auth_header = req.headers() /// request: Request<B>,
/// .get(http::header::AUTHORIZATION) /// next: Next<B>,
/// .and_then(|header| header.to_str().ok()); /// ) -> Response {
/// // do something with `request`...
/// ///
/// match auth_header { /// let response = next.run(request).await;
/// Some(auth_header) if token_is_valid(auth_header) => { ///
/// Ok(next.run(req).await) /// // do something with `response`...
///
/// response
/// } /// }
/// _ => Err(StatusCode::UNAUTHORIZED), ///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .layer(middleware::from_fn(my_middleware));
/// # let app: Router = app;
/// ```
///
/// # Running extractors
///
/// ```rust
/// use axum::{
/// Router,
/// extract::TypedHeader,
/// http::StatusCode,
/// headers::authorization::{Authorization, Bearer},
/// http::Request,
/// middleware::{self, Next},
/// response::Response,
/// routing::get,
/// };
///
/// async fn auth<B>(
/// // run the `TypedHeader` extractor
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
/// // you can also add more extractors here but the last
/// // extractor must implement `FromRequest` which
/// // `Request` does
/// request: Request<B>,
/// next: Next<B>,
/// ) -> Result<Response, StatusCode> {
/// if token_is_valid(auth.token()) {
/// let response = next.run(request).await;
/// Ok(response)
/// } else {
/// Err(StatusCode::UNAUTHORIZED)
/// } /// }
/// } /// }
/// ///
@ -59,41 +98,8 @@ use tower_service::Service;
/// # let app: Router = app; /// # let app: Router = app;
/// ``` /// ```
/// ///
/// # Running extractors
///
/// ```rust
/// use axum::{
/// Router,
/// extract::{TypedHeader, Query},
/// headers::authorization::{Authorization, Bearer},
/// http::Request,
/// middleware::{self, Next},
/// response::Response,
/// routing::get,
/// };
/// use std::collections::HashMap;
///
/// async fn my_middleware<B>(
/// TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
/// Query(query_params): Query<HashMap<String, String>>,
/// // you can add more extractors here but the last
/// // extractor must implement `FromRequest` which
/// // `Request` does
/// req: Request<B>,
/// next: Next<B>,
/// ) -> Response {
/// // do something with `auth` and `query_params`...
///
/// next.run(req).await
/// }
///
/// let app = Router::new()
/// .route("/", get(|| async { /* ... */ }))
/// .route_layer(middleware::from_fn(my_middleware));
/// # let app: Router = app;
/// ```
///
/// [extractors]: crate::extract::FromRequest /// [extractors]: crate::extract::FromRequest
/// [`State`]: crate::extract::State
pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> { pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
from_fn_with_state((), f) from_fn_with_state((), f)
} }
@ -122,13 +128,16 @@ pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
/// // you can add more extractors here but the last /// // you can add more extractors here but the last
/// // extractor must implement `FromRequest` which /// // extractor must implement `FromRequest` which
/// // `Request` does /// // `Request` does
/// req: Request<B>, /// request: Request<B>,
/// next: Next<B>, /// next: Next<B>,
/// ) -> Response { /// ) -> Response {
/// // do something with `req`... /// // do something with `request`...
/// let res = next.run(req).await; ///
/// // do something with `res`... /// let response = next.run(request).await;
/// res ///
/// // do something with `response`...
///
/// response
/// } /// }
/// ///
/// let state = AppState { /* ... */ }; /// let state = AppState { /* ... */ };