Skip to content

Commit

Permalink
Misc documentation improvements (#1647)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
  • Loading branch information
davidpdrsn and jplatte committed Jan 7, 2023
1 parent 211de92 commit 1aa357c
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 33 deletions.
2 changes: 1 addition & 1 deletion axum-core/Cargo.toml
Expand Up @@ -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"] }
Expand Down
207 changes: 207 additions & 0 deletions axum-core/src/ext_traits/request.rs
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -35,13 +135,120 @@ 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;

/// 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,
Expand Down
92 changes: 92 additions & 0 deletions axum-core/src/ext_traits/request_parts.rs
Expand Up @@ -12,13 +12,105 @@ 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;

/// 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,
Expand Down
9 changes: 9 additions & 0 deletions axum/src/docs/routing/with_state.md
Expand Up @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 1aa357c

Please sign in to comment.