From 1aa357c879c65e7885b267057de8292774e5109a Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 7 Jan 2023 14:07:54 +0100 Subject: [PATCH] Misc documentation improvements (#1647) Co-authored-by: Jonas Platte --- axum-core/Cargo.toml | 2 +- axum-core/src/ext_traits/request.rs | 207 ++++++++++++++++++++++ axum-core/src/ext_traits/request_parts.rs | 92 ++++++++++ axum/src/docs/routing/with_state.md | 9 + axum/src/middleware/from_fn.rs | 73 ++++---- 5 files changed, 350 insertions(+), 33 deletions(-) diff --git a/axum-core/Cargo.toml b/axum-core/Cargo.toml index 338f5f17de..985b674f60 100644 --- a/axum-core/Cargo.toml +++ b/axum-core/Cargo.toml @@ -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"] } diff --git a/axum-core/src/ext_traits/request.rs b/axum-core/src/ext_traits/request.rs index 05ca77ea2a..e49ba9216c 100644 --- a/axum-core/src/ext_traits/request.rs +++ b/axum-core/src/ext_traits/request.rs @@ -16,6 +16,58 @@ pub trait RequestExt: sealed::Sealed + 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); + /// + /// #[async_trait] + /// impl FromRequest for FormOrJson + /// where + /// Json: FromRequest<(), B>, + /// Form: FromRequest<(), B>, + /// T: 'static, + /// B: Send + 'static, + /// S: Send + Sync, + /// { + /// type Rejection = Response; + /// + /// async fn from_request(req: Request, _state: &S) -> Result { + /// 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::, _>() + /// .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::, _>() + /// .await + /// .map_err(|err| err.into_response())?; + /// + /// Ok(Self(payload)) + /// } else { + /// Err(StatusCode::BAD_REQUEST.into_response()) + /// } + /// } + /// } + /// ``` fn extract(self) -> BoxFuture<'static, Result> where E: FromRequest<(), B, M> + 'static, @@ -27,6 +79,54 @@ pub trait RequestExt: sealed::Sealed + 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 FromRequest for MyExtractor + /// where + /// String: FromRef, + /// S: Send + Sync, + /// B: Send + 'static, + /// { + /// type Rejection = std::convert::Infallible; + /// + /// async fn from_request(req: Request, state: &S) -> Result { + /// let requires_state = req.extract_with_state::(state).await?; + /// + /// Ok(Self { requires_state }) + /// } + /// } + /// + /// // some extractor that consumes the request body and requires state + /// struct RequiresState { /* ... */ } + /// + /// #[async_trait] + /// impl FromRequest for RequiresState + /// where + /// String: FromRef, + /// S: Send + Sync, + /// B: Send + 'static, + /// { + /// // ... + /// # type Rejection = std::convert::Infallible; + /// # async fn from_request(req: Request, _state: &S) -> Result { + /// # todo!() + /// # } + /// } + /// ``` fn extract_with_state(self, state: &S) -> BoxFuture<'_, Result> where E: FromRequest + 'static, @@ -35,6 +135,52 @@ pub trait RequestExt: sealed::Sealed + 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 { + /// bearer_token: String, + /// payload: T, + /// } + /// + /// #[async_trait] + /// impl FromRequest for MyExtractor + /// where + /// B: Send + 'static, + /// S: Send + Sync, + /// Json: FromRequest<(), B>, + /// T: 'static, + /// { + /// type Rejection = Response; + /// + /// async fn from_request(mut req: Request, _state: &S) -> Result { + /// let TypedHeader(auth_header) = req + /// .extract_parts::>>() + /// .await + /// .map_err(|err| err.into_response())?; + /// + /// let Json(payload) = req + /// .extract::, _>() + /// .await + /// .map_err(|err| err.into_response())?; + /// + /// Ok(Self { + /// bearer_token: auth_header.token().to_owned(), + /// payload, + /// }) + /// } + /// } + /// ``` fn extract_parts(&mut self) -> BoxFuture<'_, Result> where E: FromRequestParts<()> + 'static; @@ -42,6 +188,67 @@ pub trait RequestExt: sealed::Sealed + 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 { + /// requires_state: RequiresState, + /// payload: T, + /// } + /// + /// #[async_trait] + /// impl FromRequest for MyExtractor + /// where + /// String: FromRef, + /// Json: FromRequest<(), B>, + /// T: 'static, + /// S: Send + Sync, + /// B: Send + 'static, + /// { + /// type Rejection = Response; + /// + /// async fn from_request(mut req: Request, state: &S) -> Result { + /// let requires_state = req + /// .extract_parts_with_state::(state) + /// .await + /// .map_err(|err| err.into_response())?; + /// + /// let Json(payload) = req + /// .extract::, _>() + /// .await + /// .map_err(|err| err.into_response())?; + /// + /// Ok(Self { + /// requires_state, + /// payload, + /// }) + /// } + /// } + /// + /// struct RequiresState {} + /// + /// #[async_trait] + /// impl FromRequestParts for RequiresState + /// where + /// String: FromRef, + /// S: Send + Sync, + /// { + /// // ... + /// # type Rejection = std::convert::Infallible; + /// # async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + /// # todo!() + /// # } + /// } + /// ``` fn extract_parts_with_state<'a, E, S>( &'a mut self, state: &'a S, diff --git a/axum-core/src/ext_traits/request_parts.rs b/axum-core/src/ext_traits/request_parts.rs index 7e136d58f1..07a7dbff30 100644 --- a/axum-core/src/ext_traits/request_parts.rs +++ b/axum-core/src/ext_traits/request_parts.rs @@ -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, + /// } + /// + /// #[async_trait] + /// impl FromRequestParts for MyExtractor + /// where + /// S: Send + Sync, + /// { + /// type Rejection = Response; + /// + /// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + /// let user_agent = parts + /// .extract::>() + /// .await + /// .map(|user_agent| user_agent.as_str().to_owned()) + /// .map_err(|err| err.into_response())?; + /// + /// let query_params = parts + /// .extract::>>() + /// .await + /// .map(|Query(params)| params) + /// .map_err(|err| err.into_response())?; + /// + /// Ok(MyExtractor { user_agent, query_params }) + /// } + /// } + /// ``` fn extract(&mut self) -> BoxFuture<'_, Result> 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 FromRequestParts for MyExtractor + /// where + /// String: FromRef, + /// S: Send + Sync, + /// { + /// type Rejection = std::convert::Infallible; + /// + /// async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + /// let requires_state = parts + /// .extract_with_state::(state) + /// .await?; + /// + /// Ok(MyExtractor { requires_state }) + /// } + /// } + /// + /// struct RequiresState { /* ... */ } + /// + /// // some extractor that requires a `String` in the state + /// #[async_trait] + /// impl FromRequestParts for RequiresState + /// where + /// String: FromRef, + /// S: Send + Sync, + /// { + /// // ... + /// # type Rejection = std::convert::Infallible; + /// # async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + /// # unimplemented!() + /// # } + /// } + /// ``` fn extract_with_state<'a, E, S>( &'a mut self, state: &'a S, diff --git a/axum/src/docs/routing/with_state.md b/axum/src/docs/routing/with_state.md index ecaa1432b8..3f9c815132 100644 --- a/axum/src/docs/routing/with_state.md +++ b/axum/src/docs/routing/with_state.md @@ -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` means `Router` 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 diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs index 5701d70261..29885d761a 100644 --- a/axum/src/middleware/from_fn.rs +++ b/axum/src/middleware/from_fn.rs @@ -24,38 +24,35 @@ use tower_service::Service; /// 3. Take [`Next`](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(req: Request, next: Next) -> Result { -/// let auth_header = req.headers() -/// .get(http::header::AUTHORIZATION) -/// .and_then(|header| header.to_str().ok()); +/// async fn my_middleware( +/// request: Request, +/// next: Next, +/// ) -> 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; /// -/// fn token_is_valid(token: &str) -> bool { -/// // ... -/// # false +/// // do something with `response`... +/// +/// response /// } /// /// let app = Router::new() /// .route("/", get(|| async { /* ... */ })) -/// .route_layer(middleware::from_fn(auth)); +/// .layer(middleware::from_fn(my_middleware)); /// # let app: Router = app; /// ``` /// @@ -64,36 +61,45 @@ use tower_service::Service; /// ```rust /// use axum::{ /// Router, -/// extract::{TypedHeader, Query}, +/// extract::TypedHeader, +/// http::StatusCode, /// headers::authorization::{Authorization, Bearer}, /// http::Request, /// middleware::{self, Next}, /// response::Response, /// routing::get, /// }; -/// use std::collections::HashMap; /// -/// async fn my_middleware( +/// async fn auth( +/// // run the `TypedHeader` extractor /// TypedHeader(auth): TypedHeader>, -/// Query(query_params): Query>, -/// // you can add more extractors here but the last +/// // you can also add more extractors here but the last /// // extractor must implement `FromRequest` which /// // `Request` does -/// req: Request, +/// request: Request, /// next: Next, -/// ) -> Response { -/// // do something with `auth` and `query_params`... +/// ) -> Result { +/// if token_is_valid(auth.token()) { +/// let response = next.run(request).await; +/// Ok(response) +/// } else { +/// Err(StatusCode::UNAUTHORIZED) +/// } +/// } /// -/// next.run(req).await +/// fn token_is_valid(token: &str) -> bool { +/// // ... +/// # false /// } /// /// let app = Router::new() /// .route("/", get(|| async { /* ... */ })) -/// .route_layer(middleware::from_fn(my_middleware)); +/// .route_layer(middleware::from_fn(auth)); /// # let app: Router = app; /// ``` /// /// [extractors]: crate::extract::FromRequest +/// [`State`]: crate::extract::State pub fn from_fn(f: F) -> FromFnLayer { from_fn_with_state((), f) } @@ -122,13 +128,16 @@ pub fn from_fn(f: F) -> FromFnLayer { /// // you can add more extractors here but the last /// // extractor must implement `FromRequest` which /// // `Request` does -/// req: Request, +/// request: Request, /// next: Next, /// ) -> 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 { /* ... */ };