diff --git a/README.md b/README.md
index 171c1a4f..048f1216 100644
--- a/README.md
+++ b/README.md
@@ -10,10 +10,15 @@ handle(Request) -> Response`.
- 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.
- 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
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
The "Hello, World!" of tower-web is:
@@ -33,8 +38,10 @@ async fn main() {
// run it with hyper on localhost:3000
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
- let server = Server::bind(&addr).serve(Shared::new(app));
- server.await.unwrap();
+ Server::bind(&addr)
+ .serve(Shared::new(app))
+ .await
+ .unwrap();
}
```
@@ -66,7 +73,8 @@ requests"](#extracting-data-from-requests) for more details on that.
## Responses
-Anything that implements [`IntoResponse`] can be returned from a handler:
+Anything that implements [`IntoResponse`](response::IntoResponse) can be
+returned from a handler:
```rust
use tower_web::{body::Body, response::{Html, Json}, prelude::*};
@@ -107,7 +115,7 @@ async fn html(req: Request
) -> Html<&'static str> {
Html("
Hello, World!
")
}
-// `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`
async fn json(req: Request) -> Json {
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
extractor.
-[`extract::Json`] is an extractor that consumes the request body and
-deserializes as as JSON into some target type:
+For example, [`extract::Json`] is an extractor that consumes the request body and
+deserializes it as JSON into some target type:
```rust
use tower_web::prelude::*;
@@ -178,7 +186,7 @@ use uuid::Uuid;
let app = route("/users/:id", post(create_user));
async fn create_user(req: Request, 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
ecosystem of middleware:
-### To individual handlers
+### Applying middleware to individual handlers
A middleware can be applied to a single handler like so:
@@ -245,7 +253,7 @@ let app = route(
async fn handler(req: Request) {}
```
-### To groups of routes
+### Applying middleware to groups of routes
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.
For handlers created from async functions this is works automatically since
-handlers must return something that implements [`IntoResponse`], even if its
-a `Result`.
+handlers must return something that implements
+[`IntoResponse`](response::IntoResponse), even if its a `Result`.
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
use tower_web::prelude::*;
@@ -308,27 +317,27 @@ let app = route(
async fn handle(req: Request) {}
```
-The closure passed to `handle_error` must return something that implements
-`IntoResponse`.
+The closure passed to [`handle_error`](handler::Layered::handle_error) must
+return something that implements [`IntoResponse`](response::IntoResponse).
-`handle_error` is also available on a group of routes with middleware
-applied:
+[`handle_error`](routing::Layered::handle_error) is also available on a
+group of routes with middleware applied:
```rust
use tower_web::prelude::*;
-use tower::{
- BoxError, timeout::{TimeoutLayer, error::Elapsed},
-};
-use std::{borrow::Cow, time::Duration};
-use http::StatusCode;
+use tower::{BoxError, timeout::TimeoutLayer};
+use std::time::Duration;
let app = route("/", get(handle))
+ .route("/foo", post(other_handle))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.handle_error(|error: BoxError| {
// ...
});
async fn handle(req: Request) {}
+
+async fn other_handle(req: Request) {}
```
### Applying multiple middleware
@@ -416,9 +425,8 @@ tower-web also supports routing to general [`Service`]s:
```rust
use tower_web::{
- service, prelude::*,
// `ServiceExt` adds `handle_error` to any `Service`
- ServiceExt,
+ service::{self, ServiceExt}, prelude::*,
};
use tower_http::services::ServeFile;
use http::Response;
@@ -447,7 +455,7 @@ See the [`service`] module for more details.
## Nesting applications
-Applications can be nested by calling `nest`:
+Applications can be nested by calling [`nest`](routing::nest):
```rust
use tower_web::{prelude::*, routing::BoxRoute, body::BoxBody};
@@ -463,10 +471,10 @@ let app = route("/", get(|_: Request| async { /* ... */ }))
.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
-use tower_web::{prelude::*, ServiceExt, routing::nest};
+use tower_web::{prelude::*, service::ServiceExt, routing::nest};
use tower_http::services::ServeDir;
use http::Response;
use std::convert::Infallible;
@@ -482,3 +490,5 @@ let app = nest(
[tower]: https://crates.io/crates/tower
[tower-http]: https://crates.io/crates/tower-http
+[tokio]: http://crates.io/crates/tokio
+[hyper]: http://crates.io/crates/hyper
diff --git a/src/body.rs b/src/body.rs
index b490dd97..6ba65cbd 100644
--- a/src/body.rs
+++ b/src/body.rs
@@ -12,6 +12,9 @@ use tower::BoxError;
pub use hyper::body::Body;
/// 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 {
// when we've gotten rid of `BoxStdError` we should be able to change the error type to
// `BoxError`
diff --git a/src/extract/mod.rs b/src/extract/mod.rs
index b90f6058..5a870f68 100644
--- a/src/extract/mod.rs
+++ b/src/extract/mod.rs
@@ -1,4 +1,142 @@
//! Types and traits for extracting data from requests.
+//!
+//! A handler function must always take `Request` 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, payload: extract::Json) {
+//! 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) -> Result {
+//! 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, 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,
+//! // Extract captured parameters from the URL
+//! params: extract::UrlParamsMap,
+//! // Parse query string into a `HashMap`
+//! query_params: extract::Query>,
+//! // 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, payload: Option>) {
+//! 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, Json(value): Json) {
+//! // `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 async_trait::async_trait;
@@ -7,17 +145,23 @@ use http::{header, Request, Response};
use rejection::{
BodyAlreadyTaken, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8,
LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge,
- QueryStringMissing,
+ QueryStringMissing, UrlParamsAlreadyTaken,
};
use serde::de::DeserializeOwned;
use std::{collections::HashMap, convert::Infallible, str::FromStr};
pub mod rejection;
+/// Types that can be created from requests.
+///
+/// See the [module docs](crate::extract) for more details.
#[async_trait]
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;
+ /// Perform the extraction.
async fn from_request(req: &mut Request) -> Result;
}
@@ -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, pagination: extract::Query) {
+/// 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)]
pub struct Query(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, payload: extract::Json) {
+/// 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)]
pub struct Json(pub T);
@@ -61,15 +262,17 @@ where
type Rejection = Response;
async fn from_request(req: &mut Request) -> Result {
+ use bytes::Buf;
+
if has_content_type(req, "application/json") {
let body = take_body(req).map_err(IntoResponse::into_response)?;
- let bytes = hyper::body::to_bytes(body)
+ let buf = hyper::body::aggregate(body)
.await
.map_err(InvalidJsonBody::from_err)
.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(IntoResponse::into_response)?;
@@ -96,6 +299,35 @@ fn has_content_type(req: &Request, expected_content_type: &str) -> bool {
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, state: extract::Extension>) {
+/// // ...
+/// }
+///
+/// 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)]
pub struct Extension(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: extract::BytesMaxLength<1024>) {
+/// // ...
+/// }
+///
+/// let app = route("/", post(handler));
+/// ```
+///
+/// This requires the request to have a `Content-Length` header.
#[derive(Debug, Clone)]
pub struct BytesMaxLength(pub Bytes);
@@ -193,39 +440,86 @@ impl FromRequest for BytesMaxLength {
}
}
+/// Extractor that will get captures from the URL.
+///
+/// # Example
+///
+/// ```rust,no_run
+/// use tower_web::prelude::*;
+///
+/// async fn users_show(req: Request, 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)]
pub struct UrlParamsMap(HashMap);
impl UrlParamsMap {
+ /// Look up the value for a key.
pub fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).map(|s| &**s)
}
- pub fn get_typed(&self, key: &str) -> Option
+ /// Look up the value for a key and parse it into a value of type `T`.
+ pub fn get_typed(&self, key: &str) -> Option>
where
T: FromStr,
{
- self.get(key)?.parse().ok()
+ self.get(key).map(str::parse)
}
}
#[async_trait]
impl FromRequest for UrlParamsMap {
- type Rejection = MissingRouteParams;
+ type Rejection = Response;
async fn from_request(req: &mut Request) -> Result {
if let Some(params) = req
.extensions_mut()
.get_mut::