More docs

This commit is contained in:
David Pedersen 2021-06-08 21:21:20 +02:00
parent 1f8b39f05d
commit 1cf78fa807
6 changed files with 392 additions and 58 deletions

View File

@ -10,10 +10,15 @@ handle(Request) -> Response`.
- Solid foundation. tower-web is built on top of tower and makes it easy to - Solid foundation. tower-web is built on top of tower and makes it easy to
plug in any middleware from the [tower] and [tower-http] ecosystem. plug in any middleware from the [tower] and [tower-http] ecosystem.
- Focus on routing, extracting data from requests, and generating responses. - Focus on routing, extracting data from requests, and generating responses.
tower middleware can handle the rest. Tower middleware can handle the rest.
- Macro free core. Macro frameworks have their place but tower-web focuses - Macro free core. Macro frameworks have their place but tower-web focuses
on providing a core that is macro free. on providing a core that is macro free.
## Compatibility
tower-web is designed to work with [tokio] and [hyper]. Runtime and
transport layer independence is not a goal, at least for the time being.
## Example ## Example
The "Hello, World!" of tower-web is: The "Hello, World!" of tower-web is:
@ -33,8 +38,10 @@ async fn main() {
// run it with hyper on localhost:3000 // run it with hyper on localhost:3000
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let server = Server::bind(&addr).serve(Shared::new(app)); Server::bind(&addr)
server.await.unwrap(); .serve(Shared::new(app))
.await
.unwrap();
} }
``` ```
@ -66,7 +73,8 @@ requests"](#extracting-data-from-requests) for more details on that.
## Responses ## Responses
Anything that implements [`IntoResponse`] can be returned from a handler: Anything that implements [`IntoResponse`](response::IntoResponse) can be
returned from a handler:
```rust ```rust
use tower_web::{body::Body, response::{Html, Json}, prelude::*}; use tower_web::{body::Body, response::{Html, Json}, prelude::*};
@ -107,7 +115,7 @@ async fn html(req: Request<Body>) -> Html<&'static str> {
Html("<h1>Hello, World!</h1>") Html("<h1>Hello, World!</h1>")
} }
// `Json` gives a content-type of `application/json` and works with my type // `Json` gives a content-type of `application/json` and works with any type
// that implements `serde::Serialize` // that implements `serde::Serialize`
async fn json(req: Request<Body>) -> Json<Value> { async fn json(req: Request<Body>) -> Json<Value> {
Json(json!({ "data": 42 })) Json(json!({ "data": 42 }))
@ -145,8 +153,8 @@ but any arguments following are called "extractors". Any type that
implements [`FromRequest`](crate::extract::FromRequest) can be used as an implements [`FromRequest`](crate::extract::FromRequest) can be used as an
extractor. extractor.
[`extract::Json`] is an extractor that consumes the request body and For example, [`extract::Json`] is an extractor that consumes the request body and
deserializes as as JSON into some target type: deserializes it as JSON into some target type:
```rust ```rust
use tower_web::prelude::*; use tower_web::prelude::*;
@ -178,7 +186,7 @@ use uuid::Uuid;
let app = route("/users/:id", post(create_user)); let app = route("/users/:id", post(create_user));
async fn create_user(req: Request<Body>, params: extract::UrlParams<(Uuid,)>) { async fn create_user(req: Request<Body>, params: extract::UrlParams<(Uuid,)>) {
let (user_id,) = params.0; let user_id: Uuid = (params.0).0;
// ... // ...
} }
@ -229,7 +237,7 @@ See the [`extract`] module for more details.
tower-web is designed to take full advantage of the tower and tower-http tower-web is designed to take full advantage of the tower and tower-http
ecosystem of middleware: ecosystem of middleware:
### To individual handlers ### Applying middleware to individual handlers
A middleware can be applied to a single handler like so: A middleware can be applied to a single handler like so:
@ -245,7 +253,7 @@ let app = route(
async fn handler(req: Request<Body>) {} async fn handler(req: Request<Body>) {}
``` ```
### To groups of routes ### Applying middleware to groups of routes
Middleware can also be applied to a group of routes like so: Middleware can also be applied to a group of routes like so:
@ -269,11 +277,12 @@ tower-web requires all errors to be handled. That is done by using
implementations. implementations.
For handlers created from async functions this is works automatically since For handlers created from async functions this is works automatically since
handlers must return something that implements [`IntoResponse`], even if its handlers must return something that implements
a `Result`. [`IntoResponse`](response::IntoResponse), even if its a `Result`.
However middleware might add new failure cases that has to be handled. For However middleware might add new failure cases that has to be handled. For
that tower-web provides a `handle_error` combinator: that tower-web provides a [`handle_error`](handler::Layered::handle_error)
combinator:
```rust ```rust
use tower_web::prelude::*; use tower_web::prelude::*;
@ -308,27 +317,27 @@ let app = route(
async fn handle(req: Request<Body>) {} async fn handle(req: Request<Body>) {}
``` ```
The closure passed to `handle_error` must return something that implements The closure passed to [`handle_error`](handler::Layered::handle_error) must
`IntoResponse`. return something that implements [`IntoResponse`](response::IntoResponse).
`handle_error` is also available on a group of routes with middleware [`handle_error`](routing::Layered::handle_error) is also available on a
applied: group of routes with middleware applied:
```rust ```rust
use tower_web::prelude::*; use tower_web::prelude::*;
use tower::{ use tower::{BoxError, timeout::TimeoutLayer};
BoxError, timeout::{TimeoutLayer, error::Elapsed}, use std::time::Duration;
};
use std::{borrow::Cow, time::Duration};
use http::StatusCode;
let app = route("/", get(handle)) let app = route("/", get(handle))
.route("/foo", post(other_handle))
.layer(TimeoutLayer::new(Duration::from_secs(30))) .layer(TimeoutLayer::new(Duration::from_secs(30)))
.handle_error(|error: BoxError| { .handle_error(|error: BoxError| {
// ... // ...
}); });
async fn handle(req: Request<Body>) {} async fn handle(req: Request<Body>) {}
async fn other_handle(req: Request<Body>) {}
``` ```
### Applying multiple middleware ### Applying multiple middleware
@ -416,9 +425,8 @@ tower-web also supports routing to general [`Service`]s:
```rust ```rust
use tower_web::{ use tower_web::{
service, prelude::*,
// `ServiceExt` adds `handle_error` to any `Service` // `ServiceExt` adds `handle_error` to any `Service`
ServiceExt, service::{self, ServiceExt}, prelude::*,
}; };
use tower_http::services::ServeFile; use tower_http::services::ServeFile;
use http::Response; use http::Response;
@ -447,7 +455,7 @@ See the [`service`] module for more details.
## Nesting applications ## Nesting applications
Applications can be nested by calling `nest`: Applications can be nested by calling [`nest`](routing::nest):
```rust ```rust
use tower_web::{prelude::*, routing::BoxRoute, body::BoxBody}; use tower_web::{prelude::*, routing::BoxRoute, body::BoxBody};
@ -463,10 +471,10 @@ let app = route("/", get(|_: Request<Body>| async { /* ... */ }))
.nest("/api", api_routes()); .nest("/api", api_routes());
``` ```
`nest` can also be used to serve static files from a directory: [`nest`](routing::nest) can also be used to serve static files from a directory:
```rust ```rust
use tower_web::{prelude::*, ServiceExt, routing::nest}; use tower_web::{prelude::*, service::ServiceExt, routing::nest};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use http::Response; use http::Response;
use std::convert::Infallible; use std::convert::Infallible;
@ -482,3 +490,5 @@ let app = nest(
[tower]: https://crates.io/crates/tower [tower]: https://crates.io/crates/tower
[tower-http]: https://crates.io/crates/tower-http [tower-http]: https://crates.io/crates/tower-http
[tokio]: http://crates.io/crates/tokio
[hyper]: http://crates.io/crates/hyper

View File

@ -12,6 +12,9 @@ use tower::BoxError;
pub use hyper::body::Body; pub use hyper::body::Body;
/// A boxed [`Body`] trait object. /// A boxed [`Body`] trait object.
///
/// This is used in tower-web as the response body type for applications. Its necessary to unify
/// multiple response bodies types into one.
pub struct BoxBody { pub struct BoxBody {
// when we've gotten rid of `BoxStdError` we should be able to change the error type to // when we've gotten rid of `BoxStdError` we should be able to change the error type to
// `BoxError` // `BoxError`

View File

@ -1,4 +1,142 @@
//! Types and traits for extracting data from requests. //! Types and traits for extracting data from requests.
//!
//! A handler function must always take `Request<Body>` as its first argument
//! but any arguments following are called "extractors". Any type that
//! implements [`FromRequest`](FromRequest) can be used as an extractor.
//!
//! For example, [`Json`] is an extractor that consumes the request body and
//! deserializes it as JSON into some target type:
//!
//! ```rust,no_run
//! use tower_web::prelude::*;
//! use serde::Deserialize;
//!
//! #[derive(Deserialize)]
//! struct CreateUser {
//! email: String,
//! password: String,
//! }
//!
//! async fn create_user(req: Request<Body>, payload: extract::Json<CreateUser>) {
//! let payload: CreateUser = payload.0;
//!
//! // ...
//! }
//!
//! let app = route("/users", post(create_user));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # };
//! ```
//!
//! Technically extractors can also be used as "guards", for example to require
//! that requests are authorized. However the recommended way to do that is
//! using Tower middleware, such as [`tower_http::auth::RequireAuthorization`].
//! Extractors have to be applied to each handler, whereas middleware can be
//! applied to a whole stack at once, which is typically what you want for
//! authorization.
//!
//! # Defining custom extractors
//!
//! You can also define your own extractors by implementing [`FromRequest`]:
//!
//! ```rust,no_run
//! use tower_web::{async_trait, extract::FromRequest, prelude::*};
//! use http::{StatusCode, header::{HeaderValue, USER_AGENT}};
//!
//! struct ExtractUserAgent(HeaderValue);
//!
//! #[async_trait]
//! impl FromRequest for ExtractUserAgent {
//! type Rejection = (StatusCode, &'static str);
//!
//! async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
//! if let Some(user_agent) = req.headers().get(USER_AGENT) {
//! Ok(ExtractUserAgent(user_agent.clone()))
//! } else {
//! Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
//! }
//! }
//! }
//!
//! async fn handler(req: Request<Body>, user_agent: ExtractUserAgent) {
//! let user_agent: HeaderValue = user_agent.0;
//!
//! // ...
//! }
//!
//! let app = route("/foo", get(handler));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # };
//! ```
//!
//! # Multiple extractors
//!
//! Handlers can also contain multiple extractors:
//!
//! ```rust,no_run
//! use tower_web::prelude::*;
//! use std::collections::HashMap;
//!
//! async fn handler(
//! req: Request<Body>,
//! // Extract captured parameters from the URL
//! params: extract::UrlParamsMap,
//! // Parse query string into a `HashMap`
//! query_params: extract::Query<HashMap<String, String>>,
//! // Buffer the request body into a `Bytes`
//! bytes: bytes::Bytes,
//! ) {
//! // ...
//! }
//!
//! let app = route("/foo", get(handler));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # };
//! ```
//!
//! # Optional extractors
//!
//! Wrapping extractors in `Option` will make them optional:
//!
//! ```rust,no_run
//! use tower_web::{extract::Json, prelude::*};
//! use serde_json::Value;
//!
//! async fn create_user(req: Request<Body>, payload: Option<Json<Value>>) {
//! if let Some(payload) = payload {
//! // We got a valid JSON payload
//! } else {
//! // Payload wasn't valid JSON
//! }
//! }
//!
//! let app = route("/users", post(create_user));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # };
//! ```
//!
//! # Reducing boilerplate
//!
//! If you're feeling adventorous you can even deconstruct the extractors
//! directly on the function signature:
//!
//! ```rust,no_run
//! use tower_web::{extract::Json, prelude::*};
//! use serde_json::Value;
//!
//! async fn create_user(req: Request<Body>, Json(value): Json<Value>) {
//! // `value` is of type `Value`
//! }
//!
//! let app = route("/users", post(create_user));
//! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # };
//! ```
use crate::{body::Body, response::IntoResponse}; use crate::{body::Body, response::IntoResponse};
use async_trait::async_trait; use async_trait::async_trait;
@ -7,17 +145,23 @@ use http::{header, Request, Response};
use rejection::{ use rejection::{
BodyAlreadyTaken, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8, BodyAlreadyTaken, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8,
LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge, LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge,
QueryStringMissing, QueryStringMissing, UrlParamsAlreadyTaken,
}; };
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::{collections::HashMap, convert::Infallible, str::FromStr}; use std::{collections::HashMap, convert::Infallible, str::FromStr};
pub mod rejection; pub mod rejection;
/// Types that can be created from requests.
///
/// See the [module docs](crate::extract) for more details.
#[async_trait] #[async_trait]
pub trait FromRequest: Sized { pub trait FromRequest: Sized {
/// If the extractor fails it'll use this "rejection" type. A rejection is
/// a kind of error that can be converted into a response.
type Rejection: IntoResponse; type Rejection: IntoResponse;
/// Perform the extraction.
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection>; async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection>;
} }
@ -33,6 +177,34 @@ where
} }
} }
/// Extractor that deserializes query strings into some type.
///
/// `T` is expected to implement [`serde::Deserialize`].
///
/// # Example
///
/// ```rust,no_run
/// use tower_web::prelude::*;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Pagination {
/// page: usize,
/// per_page: usize,
/// }
///
/// // This will parse query strings like `?page=2&per_page=30` into `Pagination`
/// // structs.
/// async fn list_things(req: Request<Body>, pagination: extract::Query<Pagination>) {
/// let pagination: Pagination = pagination.0;
///
/// // ...
/// }
/// let app = route("/list_things", get(list_things));
/// ```
///
/// If the query string cannot be parsed it will reject the request with a `404
/// Bad Request` response.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Query<T>(pub T); pub struct Query<T>(pub T);
@ -50,6 +222,35 @@ where
} }
} }
/// Extractor that deserializes request bodies into some type.
///
/// `T` is expected to implement [`serde::Deserialize`].
///
/// # Example
///
/// ```rust,no_run
/// use tower_web::prelude::*;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct CreateUser {
/// email: String,
/// password: String,
/// }
///
/// async fn create_user(req: Request<Body>, payload: extract::Json<CreateUser>) {
/// let payload: CreateUser = payload.0;
///
/// // ...
/// }
///
/// let app = route("/users", post(create_user));
/// ```
///
/// If the query string cannot be parsed it will reject the request with a `404
/// Bad Request` response.
///
/// The request is required to have a `Content-Type: application/json` header.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Json<T>(pub T); pub struct Json<T>(pub T);
@ -61,15 +262,17 @@ where
type Rejection = Response<Body>; type Rejection = Response<Body>;
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> { async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
use bytes::Buf;
if has_content_type(req, "application/json") { if has_content_type(req, "application/json") {
let body = take_body(req).map_err(IntoResponse::into_response)?; let body = take_body(req).map_err(IntoResponse::into_response)?;
let bytes = hyper::body::to_bytes(body) let buf = hyper::body::aggregate(body)
.await .await
.map_err(InvalidJsonBody::from_err) .map_err(InvalidJsonBody::from_err)
.map_err(IntoResponse::into_response)?; .map_err(IntoResponse::into_response)?;
let value = serde_json::from_slice(&bytes) let value = serde_json::from_reader(buf.reader())
.map_err(InvalidJsonBody::from_err) .map_err(InvalidJsonBody::from_err)
.map_err(IntoResponse::into_response)?; .map_err(IntoResponse::into_response)?;
@ -96,6 +299,35 @@ fn has_content_type<B>(req: &Request<B>, expected_content_type: &str) -> bool {
content_type.starts_with(expected_content_type) content_type.starts_with(expected_content_type)
} }
/// Extractor that gets a value from request extensions.
///
/// This is commonly used to share state across handlers.
///
/// # Example
///
/// ```rust,no_run
/// use tower_web::{AddExtensionLayer, prelude::*};
/// use std::sync::Arc;
///
/// // Some shared state used throughout our application
/// struct State {
/// // ...
/// }
///
/// async fn handler(req: Request<Body>, state: extract::Extension<Arc<State>>) {
/// // ...
/// }
///
/// let state = Arc::new(State { /* ... */ });
///
/// let app = route("/", get(handler))
/// // Add middleware that inserts the state into all incoming request's
/// // extensions.
/// .layer(AddExtensionLayer::new(state));
/// ```
///
/// If the extension is missing it will reject the request with a `500 Interal
/// Server Error` response.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Extension<T>(pub T); pub struct Extension<T>(pub T);
@ -163,6 +395,21 @@ impl FromRequest for Body {
} }
} }
/// Extractor that will buffer request bodies up to a certain size.
///
/// # Example
///
/// ```rust,no_run
/// use tower_web::prelude::*;
///
/// async fn handler(req: Request<Body>, body: extract::BytesMaxLength<1024>) {
/// // ...
/// }
///
/// let app = route("/", post(handler));
/// ```
///
/// This requires the request to have a `Content-Length` header.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BytesMaxLength<const N: u64>(pub Bytes); pub struct BytesMaxLength<const N: u64>(pub Bytes);
@ -193,39 +440,86 @@ impl<const N: u64> FromRequest for BytesMaxLength<N> {
} }
} }
/// Extractor that will get captures from the URL.
///
/// # Example
///
/// ```rust,no_run
/// use tower_web::prelude::*;
///
/// async fn users_show(req: Request<Body>, params: extract::UrlParamsMap) {
/// let id: Option<&str> = params.get("id");
///
/// // ...
/// }
///
/// let app = route("/users/:id", get(users_show));
/// ```
///
/// Note that you can only have one URL params extractor per handler. If you
/// have multiple it'll response with `500 Internal Server Error`.
#[derive(Debug)] #[derive(Debug)]
pub struct UrlParamsMap(HashMap<String, String>); pub struct UrlParamsMap(HashMap<String, String>);
impl UrlParamsMap { impl UrlParamsMap {
/// Look up the value for a key.
pub fn get(&self, key: &str) -> Option<&str> { pub fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).map(|s| &**s) self.0.get(key).map(|s| &**s)
} }
pub fn get_typed<T>(&self, key: &str) -> Option<T> /// Look up the value for a key and parse it into a value of type `T`.
pub fn get_typed<T>(&self, key: &str) -> Option<Result<T, T::Err>>
where where
T: FromStr, T: FromStr,
{ {
self.get(key)?.parse().ok() self.get(key).map(str::parse)
} }
} }
#[async_trait] #[async_trait]
impl FromRequest for UrlParamsMap { impl FromRequest for UrlParamsMap {
type Rejection = MissingRouteParams; type Rejection = Response<Body>;
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> { async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
if let Some(params) = req if let Some(params) = req
.extensions_mut() .extensions_mut()
.get_mut::<Option<crate::routing::UrlParams>>() .get_mut::<Option<crate::routing::UrlParams>>()
{ {
let params = params.take().expect("params already taken").0; if let Some(params) = params.take() {
Ok(Self(params.into_iter().collect())) Ok(Self(params.0.into_iter().collect()))
} else {
Err(UrlParamsAlreadyTaken.into_response())
}
} else { } else {
Err(MissingRouteParams) Err(MissingRouteParams.into_response())
} }
} }
} }
/// Extractor that will get captures from the URL and parse them.
///
/// # Example
///
/// ```rust,no_run
/// use tower_web::{extract::UrlParams, prelude::*};
/// use uuid::Uuid;
///
/// async fn users_teams_show(
/// req: Request<Body>,
/// UrlParams(params): UrlParams<(Uuid, Uuid)>,
/// ) {
/// let user_id: Uuid = params.0;
/// let team_id: Uuid = params.1;
///
/// // ...
/// }
///
/// let app = route("/users/:user_id/team/:team_id", get(users_teams_show));
/// ```
///
/// Note that you can only have one URL params extractor per handler. If you
/// have multiple it'll response with `500 Internal Server Error`.
#[derive(Debug)]
pub struct UrlParams<T>(pub T); pub struct UrlParams<T>(pub T);
macro_rules! impl_parse_url { macro_rules! impl_parse_url {
@ -246,7 +540,11 @@ macro_rules! impl_parse_url {
.extensions_mut() .extensions_mut()
.get_mut::<Option<crate::routing::UrlParams>>() .get_mut::<Option<crate::routing::UrlParams>>()
{ {
params.take().expect("params already taken").0 if let Some(params) = params.take() {
params.0
} else {
return Err(UrlParamsAlreadyTaken.into_response());
}
} else { } else {
return Err(MissingRouteParams.into_response()) return Err(MissingRouteParams.into_response())
}; };

View File

@ -125,6 +125,13 @@ define_rejection! {
pub struct MissingRouteParams; pub struct MissingRouteParams;
} }
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "Cannot have two URL capture extractors for a single handler"]
/// Rejection type used if you try and extract the URL params more than once.
pub struct UrlParamsAlreadyTaken;
}
define_rejection! { define_rejection! {
#[status = INTERNAL_SERVER_ERROR] #[status = INTERNAL_SERVER_ERROR]
#[body = "Cannot have two request body extractors for a single handler"] #[body = "Cannot have two request body extractors for a single handler"]

View File

@ -12,6 +12,11 @@
//! - Macro free core. Macro frameworks have their place but tower-web focuses //! - Macro free core. Macro frameworks have their place but tower-web focuses
//! on providing a core that is macro free. //! on providing a core that is macro free.
//! //!
//! # Compatibility
//!
//! tower-web is designed to work with [tokio] and [hyper]. Runtime and
//! transport layer independence is not a goal, at least for the time being.
//!
//! # Example //! # Example
//! //!
//! The "Hello, World!" of tower-web is: //! The "Hello, World!" of tower-web is:
@ -31,8 +36,10 @@
//! //!
//! // run it with hyper on localhost:3000 //! // run it with hyper on localhost:3000
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); //! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//! let server = Server::bind(&addr).serve(Shared::new(app)); //! Server::bind(&addr)
//! server.await.unwrap(); //! .serve(Shared::new(app))
//! .await
//! .unwrap();
//! } //! }
//! ``` //! ```
//! //!
@ -109,7 +116,7 @@
//! Html("<h1>Hello, World!</h1>") //! Html("<h1>Hello, World!</h1>")
//! } //! }
//! //!
//! // `Json` gives a content-type of `application/json` and works with my type //! // `Json` gives a content-type of `application/json` and works with any type
//! // that implements `serde::Serialize` //! // that implements `serde::Serialize`
//! async fn json(req: Request<Body>) -> Json<Value> { //! async fn json(req: Request<Body>) -> Json<Value> {
//! Json(json!({ "data": 42 })) //! Json(json!({ "data": 42 }))
@ -150,8 +157,8 @@
//! implements [`FromRequest`](crate::extract::FromRequest) can be used as an //! implements [`FromRequest`](crate::extract::FromRequest) can be used as an
//! extractor. //! extractor.
//! //!
//! [`extract::Json`] is an extractor that consumes the request body and //! For example, [`extract::Json`] is an extractor that consumes the request body and
//! deserializes as as JSON into some target type: //! deserializes it as JSON into some target type:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use tower_web::prelude::*; //! use tower_web::prelude::*;
@ -186,7 +193,7 @@
//! let app = route("/users/:id", post(create_user)); //! let app = route("/users/:id", post(create_user));
//! //!
//! async fn create_user(req: Request<Body>, params: extract::UrlParams<(Uuid,)>) { //! async fn create_user(req: Request<Body>, params: extract::UrlParams<(Uuid,)>) {
//! let (user_id,) = params.0; //! let user_id: Uuid = (params.0).0;
//! //!
//! // ... //! // ...
//! } //! }
@ -243,7 +250,7 @@
//! tower-web is designed to take full advantage of the tower and tower-http //! tower-web is designed to take full advantage of the tower and tower-http
//! ecosystem of middleware: //! ecosystem of middleware:
//! //!
//! ## To individual handlers //! ## Applying middleware to individual handlers
//! //!
//! A middleware can be applied to a single handler like so: //! A middleware can be applied to a single handler like so:
//! //!
@ -262,7 +269,7 @@
//! # }; //! # };
//! ``` //! ```
//! //!
//! ## To groups of routes //! ## Applying middleware to groups of routes
//! //!
//! Middleware can also be applied to a group of routes like so: //! Middleware can also be applied to a group of routes like so:
//! //!
@ -293,7 +300,8 @@
//! [`IntoResponse`](response::IntoResponse), even if its a `Result`. //! [`IntoResponse`](response::IntoResponse), even if its a `Result`.
//! //!
//! However middleware might add new failure cases that has to be handled. For //! However middleware might add new failure cases that has to be handled. For
//! that tower-web provides a `handle_error` combinator: //! that tower-web provides a [`handle_error`](handler::Layered::handle_error)
//! combinator:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use tower_web::prelude::*; //! use tower_web::prelude::*;
@ -331,27 +339,27 @@
//! # }; //! # };
//! ``` //! ```
//! //!
//! The closure passed to `handle_error` must return something that implements //! The closure passed to [`handle_error`](handler::Layered::handle_error) must
//! `IntoResponse`. //! return something that implements [`IntoResponse`](response::IntoResponse).
//! //!
//! `handle_error` is also available on a group of routes with middleware //! [`handle_error`](routing::Layered::handle_error) is also available on a
//! applied: //! group of routes with middleware applied:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use tower_web::prelude::*; //! use tower_web::prelude::*;
//! use tower::{ //! use tower::{BoxError, timeout::TimeoutLayer};
//! BoxError, timeout::{TimeoutLayer, error::Elapsed}, //! use std::time::Duration;
//! };
//! use std::{borrow::Cow, time::Duration};
//! use http::StatusCode;
//! //!
//! let app = route("/", get(handle)) //! let app = route("/", get(handle))
//! .route("/foo", post(other_handle))
//! .layer(TimeoutLayer::new(Duration::from_secs(30))) //! .layer(TimeoutLayer::new(Duration::from_secs(30)))
//! .handle_error(|error: BoxError| { //! .handle_error(|error: BoxError| {
//! // ... //! // ...
//! }); //! });
//! //!
//! async fn handle(req: Request<Body>) {} //! async fn handle(req: Request<Body>) {}
//!
//! async fn other_handle(req: Request<Body>) {}
//! # async { //! # async {
//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; //! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await;
//! # }; //! # };
@ -481,7 +489,7 @@
//! //!
//! # Nesting applications //! # Nesting applications
//! //!
//! Applications can be nested by calling `nest`: //! Applications can be nested by calling [`nest`](routing::nest):
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use tower_web::{prelude::*, routing::BoxRoute, body::BoxBody}; //! use tower_web::{prelude::*, routing::BoxRoute, body::BoxBody};
@ -500,7 +508,7 @@
//! # }; //! # };
//! ``` //! ```
//! //!
//! `nest` can also be used to serve static files from a directory: //! [`nest`](routing::nest) can also be used to serve static files from a directory:
//! //!
//! ```rust,no_run //! ```rust,no_run
//! use tower_web::{prelude::*, service::ServiceExt, routing::nest}; //! use tower_web::{prelude::*, service::ServiceExt, routing::nest};
@ -522,6 +530,8 @@
//! //!
//! [tower]: https://crates.io/crates/tower //! [tower]: https://crates.io/crates/tower
//! [tower-http]: https://crates.io/crates/tower-http //! [tower-http]: https://crates.io/crates/tower-http
//! [tokio]: http://crates.io/crates/tokio
//! [hyper]: http://crates.io/crates/hyper
// #![doc(html_root_url = "https://docs.rs/tower-http/0.1.0")] // #![doc(html_root_url = "https://docs.rs/tower-http/0.1.0")]
#![warn( #![warn(

View File

@ -248,7 +248,13 @@ async fn extracting_url_params() {
.post( .post(
|_: Request<Body>, params_map: extract::UrlParamsMap| async move { |_: Request<Body>, params_map: extract::UrlParamsMap| async move {
assert_eq!(params_map.get("id").unwrap(), "1337"); assert_eq!(params_map.get("id").unwrap(), "1337");
assert_eq!(params_map.get_typed::<i32>("id").unwrap(), 1337); assert_eq!(
params_map
.get_typed::<i32>("id")
.expect("missing")
.expect("failed to parse"),
1337
);
}, },
), ),
); );