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"
[dev-dependencies]
axum = { path = "../axum", version = "0.6.0" }
axum = { path = "../axum", version = "0.6.0", features = ["headers"] }
futures-util = "0.3"
hyper = "0.14"
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
/// 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>>
where
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
/// 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>>
where
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`.
///
/// 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>>
where
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`.
///
/// 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>(
&'a mut self,
state: &'a S,

View file

@ -12,6 +12,49 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
/// Apply an extractor to this `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>>
where
E: FromRequestParts<()> + 'static;
@ -19,6 +62,55 @@ pub trait RequestPartsExt: sealed::Sealed + Sized {
/// Apply an extractor that requires some state to this `Parts`.
///
/// 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>(
&'a mut self,
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
`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`]
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.
/// 4. Return something that implements [`IntoResponse`].
///
/// Note that this function doesn't support extracting [`State`]. For that, use [`from_fn_with_state`].
///
/// # Example
///
/// ```rust
/// use axum::{
/// Router,
/// http::{self, Request, StatusCode},
/// http::{self, Request},
/// routing::get,
/// response::{IntoResponse, Response},
/// response::Response,
/// middleware::{self, Next},
/// };
///
/// async fn auth<B>(req: Request<B>, next: Next<B>) -> Result<Response, StatusCode> {
/// let auth_header = req.headers()
/// .get(http::header::AUTHORIZATION)
/// .and_then(|header| header.to_str().ok());
/// async fn my_middleware<B>(
/// request: Request<B>,
/// next: Next<B>,
/// ) -> Response {
/// // do something with `request`...
///
/// match auth_header {
/// Some(auth_header) if token_is_valid(auth_header) => {
/// Ok(next.run(req).await)
/// }
/// _ => Err(StatusCode::UNAUTHORIZED),
/// let response = next.run(request).await;
///
/// // do something with `response`...
///
/// response
/// }
///
/// 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;
/// ```
///
/// # 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
/// [`State`]: crate::extract::State
pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
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
/// // extractor must implement `FromRequest` which
/// // `Request` does
/// req: Request<B>,
/// request: Request<B>,
/// next: Next<B>,
/// ) -> Response {
/// // do something with `req`...
/// let res = next.run(req).await;
/// // do something with `res`...
/// res
/// // do something with `request`...
///
/// let response = next.run(request).await;
///
/// // do something with `response`...
///
/// response
/// }
///
/// let state = AppState { /* ... */ };