diff --git a/README.md b/README.md index 048f1216..c6fea0cd 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,7 @@ use tower::make::Shared; #[tokio::main] async fn main() { // build our application with a single route - let app = route("/", get(|request: Request| async { - "Hello, World!" - })); + let app = route("/", get(|| async { "Hello, World!" })); // run it with hyper on localhost:3000 let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -55,15 +53,15 @@ use tower_web::prelude::*; let app = route("/", get(get_slash).post(post_slash)) .route("/foo", get(get_foo)); -async fn get_slash(req: Request) { +async fn get_slash() { // `GET /` called } -async fn post_slash(req: Request) { +async fn post_slash() { // `POST /` called } -async fn get_foo(req: Request) { +async fn get_foo() { // `GET /foo` called } ``` @@ -78,57 +76,57 @@ returned from a handler: ```rust use tower_web::{body::Body, response::{Html, Json}, prelude::*}; -use http::{StatusCode, Response}; +use http::{StatusCode, Response, Uri}; use serde_json::{Value, json}; // We've already seen returning &'static str -async fn plain_text(req: Request) -> &'static str { +async fn plain_text() -> &'static str { "foo" } // String works too and will get a text/plain content-type -async fn plain_text_string(req: Request) -> String { - format!("Hi from {}", req.uri().path()) +async fn plain_text_string(uri: Uri) -> String { + format!("Hi from {}", uri.path()) } // Bytes will get a `application/octet-stream` content-type -async fn bytes(req: Request) -> Vec { +async fn bytes() -> Vec { vec![1, 2, 3, 4] } // `()` gives an empty response -async fn empty(req: Request) {} +async fn empty() {} // `StatusCode` gives an empty response with that status code -async fn empty_with_status(req: Request) -> StatusCode { +async fn empty_with_status() -> StatusCode { StatusCode::NOT_FOUND } // A tuple of `StatusCode` and something that implements `IntoResponse` can // be used to override the status code -async fn with_status(req: Request) -> (StatusCode, &'static str) { +async fn with_status() -> (StatusCode, &'static str) { (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong") } // `Html` gives a content-type of `text/html` -async fn html(req: Request) -> Html<&'static str> { +async fn html() -> Html<&'static str> { Html("

Hello, World!

") } // `Json` gives a content-type of `application/json` and works with any type // that implements `serde::Serialize` -async fn json(req: Request) -> Json { +async fn json() -> Json { Json(json!({ "data": 42 })) } // `Result` where `T` and `E` implement `IntoResponse` is useful for // returning errors -async fn result(req: Request) -> Result<&'static str, StatusCode> { +async fn result() -> Result<&'static str, StatusCode> { Ok("all good") } // `Response` gives full control -async fn response(req: Request) -> Response { +async fn response() -> Response { Response::builder().body(Body::empty()).unwrap() } @@ -148,13 +146,12 @@ See the [`response`] module for more details. ## 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`](crate::extract::FromRequest) can be used as an -extractor. +A handler function is an async function take takes any number of +"extractors" as arguments. An extractor is a type that implements +[`FromRequest`](crate::extract::FromRequest). -For example, [`extract::Json`] is an extractor that consumes the request body and -deserializes it 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::*; @@ -168,7 +165,7 @@ struct CreateUser { password: String, } -async fn create_user(req: Request, payload: extract::Json) { +async fn create_user(payload: extract::Json) { let payload: CreateUser = payload.0; // ... @@ -185,7 +182,7 @@ use uuid::Uuid; let app = route("/users/:id", post(create_user)); -async fn create_user(req: Request, params: extract::UrlParams<(Uuid,)>) { +async fn create_user(params: extract::UrlParams<(Uuid,)>) { let user_id: Uuid = (params.0).0; // ... @@ -217,7 +214,6 @@ impl Default for Pagination { } async fn get_user_things( - req: Request, params: extract::UrlParams<(Uuid,)>, pagination: Option>, ) { @@ -228,6 +224,21 @@ async fn get_user_things( } ``` +Additionally `Request` is itself an extractor: + +```rust +use tower_web::prelude::*; + +let app = route("/users/:id", post(handler)); + +async fn handler(req: Request) { + // ... +} +``` + +However it cannot be combined with other extractors since it consumes the +entire request. + See the [`extract`] module for more details. [`Uuid`]: https://docs.rs/uuid/latest/uuid/ @@ -250,7 +261,7 @@ let app = route( get(handler.layer(ConcurrencyLimitLayer::new(100))), ); -async fn handler(req: Request) {} +async fn handler() {} ``` ### Applying middleware to groups of routes @@ -265,9 +276,9 @@ let app = route("/", get(get_slash)) .route("/foo", post(post_foo)) .layer(ConcurrencyLimitLayer::new(100)); -async fn get_slash(req: Request) {} +async fn get_slash() {} -async fn post_foo(req: Request) {} +async fn post_foo() {} ``` ### Error handling @@ -314,7 +325,7 @@ let app = route( })), ); -async fn handle(req: Request) {} +async fn handle() {} ``` The closure passed to [`handle_error`](handler::Layered::handle_error) must @@ -335,9 +346,9 @@ let app = route("/", get(handle)) // ... }); -async fn handle(req: Request) {} +async fn handle() {} -async fn other_handle(req: Request) {} +async fn other_handle() {} ``` ### Applying multiple middleware @@ -410,7 +421,6 @@ let shared_state = Arc::new(State { /* ... */ }); let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state)); async fn handler( - req: Request, state: extract::Extension>, ) { let state: Arc = state.0; @@ -451,7 +461,8 @@ let app = route( ); ``` -See the [`service`] module for more details. +Routing to arbitrary services in this way has complications for backpressure +([`Service::poll_ready`]). See the [`service`] module for more details. ## Nesting applications diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 3d6f9d0f..dfdf7f97 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,4 +1,4 @@ -use http::{Request, StatusCode}; +use http::StatusCode; use hyper::Server; use std::net::SocketAddr; use tower::make::Shared; @@ -18,11 +18,11 @@ async fn main() { server.await.unwrap(); } -async fn handler(_req: Request) -> response::Html<&'static str> { +async fn handler() -> response::Html<&'static str> { response::Html("

Hello, World!

") } -async fn greet(_req: Request, params: extract::UrlParamsMap) -> Result { +async fn greet(params: extract::UrlParamsMap) -> Result { if let Some(name) = params.get("name") { Ok(format!("Hello {}!", name)) } else { diff --git a/examples/key_value_store.rs b/examples/key_value_store.rs index f76ab510..f8a2cd2e 100644 --- a/examples/key_value_store.rs +++ b/examples/key_value_store.rs @@ -7,7 +7,7 @@ //! ``` use bytes::Bytes; -use http::{Request, StatusCode}; +use http::StatusCode; use hyper::Server; use std::{ borrow::Cow, @@ -22,7 +22,7 @@ use tower_http::{ compression::CompressionLayer, trace::TraceLayer, }; use tower_web::{ - body::{Body, BoxBody}, + body::BoxBody, extract::{ContentLengthLimit, Extension, UrlParams}, prelude::*, response::IntoResponse, @@ -72,7 +72,6 @@ struct State { } async fn kv_get( - _req: Request, UrlParams((key,)): UrlParams<(String,)>, Extension(state): Extension, ) -> Result { @@ -86,7 +85,6 @@ async fn kv_get( } async fn kv_set( - _req: Request, UrlParams((key,)): UrlParams<(String,)>, ContentLengthLimit(bytes): ContentLengthLimit, // ~5mb Extension(state): Extension, @@ -94,7 +92,7 @@ async fn kv_set( state.write().unwrap().db.insert(key, bytes); } -async fn list_keys(_req: Request, Extension(state): Extension) -> String { +async fn list_keys(Extension(state): Extension) -> String { let db = &state.read().unwrap().db; db.keys() @@ -104,12 +102,11 @@ async fn list_keys(_req: Request, Extension(state): Extension } fn admin_routes() -> BoxRoute { - async fn delete_all_keys(_req: Request, Extension(state): Extension) { + async fn delete_all_keys(Extension(state): Extension) { state.write().unwrap().db.clear(); } async fn remove_key( - _req: Request, UrlParams((key,)): UrlParams<(String,)>, Extension(state): Extension, ) { diff --git a/src/extract/mod.rs b/src/extract/mod.rs index bebb5257..d0ccc76a 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -1,8 +1,8 @@ //! 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. +//! A handler function is an async function take takes any number of +//! "extractors" as arguments. An extractor is a type that implements +//! [`FromRequest`](crate::extract::FromRequest). //! //! For example, [`Json`] is an extractor that consumes the request body and //! deserializes it as JSON into some target type: @@ -17,7 +17,7 @@ //! password: String, //! } //! -//! async fn create_user(req: Request, payload: extract::Json) { +//! async fn create_user(payload: extract::Json) { //! let payload: CreateUser = payload.0; //! //! // ... @@ -52,7 +52,7 @@ //! } //! } //! -//! async fn handler(req: Request, user_agent: ExtractUserAgent) { +//! async fn handler(user_agent: ExtractUserAgent) { //! let user_agent: HeaderValue = user_agent.0; //! //! // ... @@ -73,7 +73,6 @@ //! use std::collections::HashMap; //! //! async fn handler( -//! req: Request, //! // Extract captured parameters from the URL //! params: extract::UrlParamsMap, //! // Parse query string into a `HashMap` @@ -98,7 +97,7 @@ //! use tower_web::{extract::Json, prelude::*}; //! use serde_json::Value; //! -//! async fn create_user(req: Request, payload: Option>) { +//! async fn create_user(payload: Option>) { //! if let Some(payload) = payload { //! // We got a valid JSON payload //! } else { @@ -121,7 +120,7 @@ //! use tower_web::{extract::Json, prelude::*}; //! use serde_json::Value; //! -//! async fn create_user(req: Request, Json(value): Json) { +//! async fn create_user(Json(value): Json) { //! // `value` is of type `Value` //! } //! @@ -134,14 +133,14 @@ use crate::{body::Body, response::IntoResponse}; use async_trait::async_trait; use bytes::Bytes; -use http::{header, Request, Response}; +use http::{HeaderMap, Method, Request, Response, Uri, Version, header}; use rejection::{ - BodyAlreadyTaken, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8, + BodyAlreadyExtracted, FailedToBufferBody, InvalidJsonBody, InvalidUrlParam, InvalidUtf8, LengthRequired, MissingExtension, MissingJsonContentType, MissingRouteParams, PayloadTooLarge, - QueryStringMissing, UrlParamsAlreadyTaken, + QueryStringMissing, RequestAlreadyExtracted, UrlParamsAlreadyExtracted, }; use serde::de::DeserializeOwned; -use std::{collections::HashMap, convert::Infallible, str::FromStr}; +use std::{collections::HashMap, mem, convert::Infallible, str::FromStr}; pub mod rejection; @@ -188,7 +187,7 @@ where /// /// // This will parse query strings like `?page=2&per_page=30` into `Pagination` /// // structs. -/// async fn list_things(req: Request, pagination: extract::Query) { +/// async fn list_things(pagination: extract::Query) { /// let pagination: Pagination = pagination.0; /// /// // ... @@ -231,7 +230,7 @@ where /// password: String, /// } /// -/// async fn create_user(req: Request, payload: extract::Json) { +/// async fn create_user(payload: extract::Json) { /// let payload: CreateUser = payload.0; /// /// // ... @@ -307,7 +306,7 @@ fn has_content_type(req: &Request, expected_content_type: &str) -> bool { /// // ... /// } /// -/// async fn handler(req: Request, state: extract::Extension>) { +/// async fn handler(state: extract::Extension>) { /// // ... /// } /// @@ -381,13 +380,68 @@ impl FromRequest for String { #[async_trait] impl FromRequest for Body { - type Rejection = BodyAlreadyTaken; + type Rejection = BodyAlreadyExtracted; async fn from_request(req: &mut Request) -> Result { take_body(req) } } +#[async_trait] +impl FromRequest for Request { + type Rejection = RequestAlreadyExtracted; + + async fn from_request(req: &mut Request) -> Result { + struct RequestAlreadyExtractedExt; + + if req + .extensions_mut() + .insert(RequestAlreadyExtractedExt) + .is_some() + { + Err(RequestAlreadyExtracted) + } else { + Ok(mem::take(req)) + } + } +} + +#[async_trait] +impl FromRequest for Method { + type Rejection = Infallible; + + async fn from_request(req: &mut Request) -> Result { + Ok(req.method().clone()) + } +} + +#[async_trait] +impl FromRequest for Uri { + type Rejection = Infallible; + + async fn from_request(req: &mut Request) -> Result { + Ok(req.uri().clone()) + } +} + +#[async_trait] +impl FromRequest for Version { + type Rejection = Infallible; + + async fn from_request(req: &mut Request) -> Result { + Ok(req.version()) + } +} + +#[async_trait] +impl FromRequest for HeaderMap { + type Rejection = Infallible; + + async fn from_request(req: &mut Request) -> Result { + Ok(mem::take(req.headers_mut())) + } +} + /// Extractor that will reject requests with a body larger than some size. /// /// # Example @@ -395,7 +449,7 @@ impl FromRequest for Body { /// ```rust,no_run /// use tower_web::prelude::*; /// -/// async fn handler(req: Request, body: extract::ContentLengthLimit) { +/// async fn handler(body: extract::ContentLengthLimit) { /// // ... /// } /// @@ -442,7 +496,7 @@ where /// ```rust,no_run /// use tower_web::prelude::*; /// -/// async fn users_show(req: Request, params: extract::UrlParamsMap) { +/// async fn users_show(params: extract::UrlParamsMap) { /// let id: Option<&str> = params.get("id"); /// /// // ... @@ -483,7 +537,7 @@ impl FromRequest for UrlParamsMap { if let Some(params) = params.take() { Ok(Self(params.0.into_iter().collect())) } else { - Err(UrlParamsAlreadyTaken.into_response()) + Err(UrlParamsAlreadyExtracted.into_response()) } } else { Err(MissingRouteParams.into_response()) @@ -500,7 +554,6 @@ impl FromRequest for UrlParamsMap { /// use uuid::Uuid; /// /// async fn users_teams_show( -/// req: Request, /// UrlParams(params): UrlParams<(Uuid, Uuid)>, /// ) { /// let user_id: Uuid = params.0; @@ -538,7 +591,7 @@ macro_rules! impl_parse_url { if let Some(params) = params.take() { params.0 } else { - return Err(UrlParamsAlreadyTaken.into_response()); + return Err(UrlParamsAlreadyExtracted.into_response()); } } else { return Err(MissingRouteParams.into_response()) @@ -572,14 +625,17 @@ macro_rules! impl_parse_url { impl_parse_url!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16); -fn take_body(req: &mut Request) -> Result { - struct BodyAlreadyTakenExt; +fn take_body(req: &mut Request) -> Result { + struct BodyAlreadyExtractedExt; - if req.extensions_mut().insert(BodyAlreadyTakenExt).is_some() { - Err(BodyAlreadyTaken) + if req + .extensions_mut() + .insert(BodyAlreadyExtractedExt) + .is_some() + { + Err(BodyAlreadyExtracted) } else { - let body = std::mem::take(req.body_mut()); - Ok(body) + Ok(mem::take(req.body_mut())) } } diff --git a/src/extract/rejection.rs b/src/extract/rejection.rs index af388d77..ef961cab 100644 --- a/src/extract/rejection.rs +++ b/src/extract/rejection.rs @@ -129,7 +129,7 @@ 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; + pub struct UrlParamsAlreadyExtracted; } define_rejection! { @@ -137,7 +137,14 @@ define_rejection! { #[body = "Cannot have two request body extractors for a single handler"] /// Rejection type used if you try and extract the request body more than /// once. - pub struct BodyAlreadyTaken; + pub struct BodyAlreadyExtracted; +} + +define_rejection! { + #[status = INTERNAL_SERVER_ERROR] + #[body = "Cannot have two `Request` extractors for a single handler"] + /// Rejection type used if you try and extract the request more than once. + pub struct RequestAlreadyExtracted; } /// Rejection type for [`UrlParams`](super::UrlParams) if the capture route diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 32d5b57c..48c8d9a1 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -2,13 +2,9 @@ //! //! # What is a handler? //! -//! In tower-web a "handler" is an async function that accepts a request and -//! produces a response. Handler functions must take -//! `http::Request` as they first argument and return -//! something that implements [`IntoResponse`]. -//! -//! Additionally handlers can use ["extractors"](crate::extract) to extract data -//! from incoming requests. +//! In tower-web a "handler" is an async function that accepts zero or more +//! ["extractors"](crate::extract) as arguments and returns something that +//! implements [`IntoResponse`]. //! //! # Example //! @@ -19,17 +15,17 @@ //! use bytes::Bytes; //! use http::StatusCode; //! -//! // Handlers must take `Request` as the first argument and must return -//! // something that implements `IntoResponse`, which `()` does -//! async fn unit_handler(request: Request) {} +//! // Handler that immediately returns an empty `200 OK` response. +//! async fn unit_handler() {} //! -//! // `String` also implements `IntoResponse` -//! async fn string_handler(request: Request) -> String { +//! // Handler that immediately returns an empty `200 Ok` response with a plain +//! /// text body. +//! async fn string_handler() -> String { //! "Hello, World!".to_string() //! } //! //! // Handler that buffers the request body and returns it if it is valid UTF-8 -//! async fn buffer_body(request: Request, body: Bytes) -> Result { +//! async fn buffer_body(body: Bytes) -> Result { //! if let Ok(string) = String::from_utf8(body.to_vec()) { //! Ok(string) //! } else { @@ -72,7 +68,7 @@ pub mod future; /// ```rust /// use tower_web::prelude::*; /// -/// async fn handler(request: Request) {} +/// async fn handler() {} /// /// // All requests to `/` will go to `handler` regardless of the HTTP method. /// let app = route("/", any(handler)); @@ -111,7 +107,7 @@ where /// ```rust /// use tower_web::prelude::*; /// -/// async fn handler(request: Request) {} +/// async fn handler() {} /// /// // Requests to `GET /` will go to `handler`. /// let app = route("/", get(handler)); @@ -190,7 +186,7 @@ where /// ```rust /// use tower_web::{handler::on, routing::MethodFilter, prelude::*}; /// -/// async fn handler(request: Request) {} +/// async fn handler() {} /// /// // Requests to `POST /` will go to `handler`. /// let app = route("/", on(MethodFilter::Post, handler)); @@ -250,7 +246,7 @@ pub trait Handler: Sized { /// use tower_web::prelude::*; /// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit}; /// - /// async fn handler(request: Request) { /* ... */ } + /// async fn handler() { /* ... */ } /// /// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64)); /// ``` @@ -273,14 +269,14 @@ pub trait Handler: Sized { #[async_trait] impl Handler<()> for F where - F: FnOnce(Request) -> Fut + Send + Sync, + F: FnOnce() -> Fut + Send + Sync, Fut: Future + Send, Res: IntoResponse, { type Sealed = sealed::Hidden; - async fn call(self, req: Request) -> Response { - self(req).await.into_response().map(BoxBody::new) + async fn call(self, _req: Request) -> Response { + self().await.into_response().map(BoxBody::new) } } @@ -292,7 +288,7 @@ macro_rules! impl_handler { #[allow(non_snake_case)] impl Handler<($head, $($tail,)*)> for F where - F: FnOnce(Request, $head, $($tail,)*) -> Fut + Send + Sync, + F: FnOnce($head, $($tail,)*) -> Fut + Send + Sync, Fut: Future + Send, Res: IntoResponse, $head: FromRequest + Send, @@ -313,7 +309,7 @@ macro_rules! impl_handler { }; )* - let res = self(req, $head, $($tail,)*).await; + let res = self($head, $($tail,)*).await; res.into_response().map(BoxBody::new) } @@ -398,7 +394,7 @@ impl Layered { /// use tower::{BoxError, timeout::TimeoutLayer}; /// use std::time::Duration; /// - /// async fn handler(request: Request) { /* ... */ } + /// async fn handler() { /* ... */ } /// /// // `Timeout` will fail with `BoxError` if the timeout elapses... /// let layered_handler = handler @@ -545,9 +541,9 @@ impl OnMethod { /// ```rust /// use tower_web::prelude::*; /// - /// async fn handler(request: Request) {} + /// async fn handler() {} /// - /// async fn other_handler(request: Request) {} + /// async fn other_handler() {} /// /// // Requests to `GET /` will go to `handler` and `POST /` will go to /// // `other_handler`. @@ -628,9 +624,9 @@ impl OnMethod { /// ```rust /// use tower_web::{routing::MethodFilter, prelude::*}; /// - /// async fn handler(request: Request) {} + /// async fn handler() {} /// - /// async fn other_handler(request: Request) {} + /// async fn other_handler() {} /// /// // Requests to `GET /` will go to `handler` and `DELETE /` will go to /// // `other_handler` diff --git a/src/lib.rs b/src/lib.rs index 6ec28d50..28b0b638 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,9 +30,7 @@ //! #[tokio::main] //! async fn main() { //! // build our application with a single route -//! let app = route("/", get(|request: Request| async { -//! "Hello, World!" -//! })); +//! let app = route("/", get(|| async { "Hello, World!" })); //! //! // run it with hyper on localhost:3000 //! let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -53,15 +51,15 @@ //! let app = route("/", get(get_slash).post(post_slash)) //! .route("/foo", get(get_foo)); //! -//! async fn get_slash(req: Request) { +//! async fn get_slash() { //! // `GET /` called //! } //! -//! async fn post_slash(req: Request) { +//! async fn post_slash() { //! // `POST /` called //! } //! -//! async fn get_foo(req: Request) { +//! async fn get_foo() { //! // `GET /foo` called //! } //! # async { @@ -79,57 +77,57 @@ //! //! ```rust,no_run //! use tower_web::{body::Body, response::{Html, Json}, prelude::*}; -//! use http::{StatusCode, Response}; +//! use http::{StatusCode, Response, Uri}; //! use serde_json::{Value, json}; //! //! // We've already seen returning &'static str -//! async fn plain_text(req: Request) -> &'static str { +//! async fn plain_text() -> &'static str { //! "foo" //! } //! //! // String works too and will get a text/plain content-type -//! async fn plain_text_string(req: Request) -> String { -//! format!("Hi from {}", req.uri().path()) +//! async fn plain_text_string(uri: Uri) -> String { +//! format!("Hi from {}", uri.path()) //! } //! //! // Bytes will get a `application/octet-stream` content-type -//! async fn bytes(req: Request) -> Vec { +//! async fn bytes() -> Vec { //! vec![1, 2, 3, 4] //! } //! //! // `()` gives an empty response -//! async fn empty(req: Request) {} +//! async fn empty() {} //! //! // `StatusCode` gives an empty response with that status code -//! async fn empty_with_status(req: Request) -> StatusCode { +//! async fn empty_with_status() -> StatusCode { //! StatusCode::NOT_FOUND //! } //! //! // A tuple of `StatusCode` and something that implements `IntoResponse` can //! // be used to override the status code -//! async fn with_status(req: Request) -> (StatusCode, &'static str) { +//! async fn with_status() -> (StatusCode, &'static str) { //! (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong") //! } //! //! // `Html` gives a content-type of `text/html` -//! async fn html(req: Request) -> Html<&'static str> { +//! async fn html() -> Html<&'static str> { //! Html("

Hello, World!

") //! } //! //! // `Json` gives a content-type of `application/json` and works with any type //! // that implements `serde::Serialize` -//! async fn json(req: Request) -> Json { +//! async fn json() -> Json { //! Json(json!({ "data": 42 })) //! } //! //! // `Result` where `T` and `E` implement `IntoResponse` is useful for //! // returning errors -//! async fn result(req: Request) -> Result<&'static str, StatusCode> { +//! async fn result() -> Result<&'static str, StatusCode> { //! Ok("all good") //! } //! //! // `Response` gives full control -//! async fn response(req: Request) -> Response { +//! async fn response() -> Response { //! Response::builder().body(Body::empty()).unwrap() //! } //! @@ -152,13 +150,12 @@ //! //! # 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`](crate::extract::FromRequest) can be used as an -//! extractor. +//! A handler function is an async function take takes any number of +//! "extractors" as arguments. An extractor is a type that implements +//! [`FromRequest`](crate::extract::FromRequest). //! -//! For example, [`extract::Json`] is an extractor that consumes the request body and -//! deserializes it 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,no_run //! use tower_web::prelude::*; @@ -172,7 +169,7 @@ //! password: String, //! } //! -//! async fn create_user(req: Request, payload: extract::Json) { +//! async fn create_user(payload: extract::Json) { //! let payload: CreateUser = payload.0; //! //! // ... @@ -192,7 +189,7 @@ //! //! let app = route("/users/:id", post(create_user)); //! -//! async fn create_user(req: Request, params: extract::UrlParams<(Uuid,)>) { +//! async fn create_user(params: extract::UrlParams<(Uuid,)>) { //! let user_id: Uuid = (params.0).0; //! //! // ... @@ -227,7 +224,6 @@ //! } //! //! async fn get_user_things( -//! req: Request, //! params: extract::UrlParams<(Uuid,)>, //! pagination: Option>, //! ) { @@ -241,6 +237,24 @@ //! # }; //! ``` //! +//! Additionally `Request` is itself an extractor: +//! +//! ```rust,no_run +//! use tower_web::prelude::*; +//! +//! let app = route("/users/:id", post(handler)); +//! +//! async fn handler(req: Request) { +//! // ... +//! } +//! # async { +//! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; +//! # }; +//! ``` +//! +//! However it cannot be combined with other extractors since it consumes the +//! entire request. +//! //! See the [`extract`] module for more details. //! //! [`Uuid`]: https://docs.rs/uuid/latest/uuid/ @@ -263,7 +277,7 @@ //! get(handler.layer(ConcurrencyLimitLayer::new(100))), //! ); //! -//! async fn handler(req: Request) {} +//! async fn handler() {} //! # async { //! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; //! # }; @@ -281,9 +295,9 @@ //! .route("/foo", post(post_foo)) //! .layer(ConcurrencyLimitLayer::new(100)); //! -//! async fn get_slash(req: Request) {} +//! async fn get_slash() {} //! -//! async fn post_foo(req: Request) {} +//! async fn post_foo() {} //! # async { //! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; //! # }; @@ -333,7 +347,7 @@ //! })), //! ); //! -//! async fn handle(req: Request) {} +//! async fn handle() {} //! # async { //! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; //! # }; @@ -357,9 +371,9 @@ //! // ... //! }); //! -//! async fn handle(req: Request) {} +//! async fn handle() {} //! -//! async fn other_handle(req: Request) {} +//! async fn other_handle() {} //! # async { //! # hyper::Server::bind(&"".parse().unwrap()).serve(tower::make::Shared::new(app)).await; //! # }; @@ -438,7 +452,6 @@ //! let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state)); //! //! async fn handler( -//! req: Request, //! state: extract::Extension>, //! ) { //! let state: Arc = state.0; diff --git a/src/routing.rs b/src/routing.rs index 165ea740..9d4c3e45 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -88,11 +88,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized { /// ```rust /// use tower_web::prelude::*; /// - /// async fn first_handler(request: Request) { /* ... */ } + /// async fn first_handler() { /* ... */ } /// - /// async fn second_handler(request: Request) { /* ... */ } + /// async fn second_handler() { /* ... */ } /// - /// async fn third_handler(request: Request) { /* ... */ } + /// async fn third_handler() { /* ... */ } /// /// // `GET /` goes to `first_handler`, `POST /` goes to `second_handler`, /// // and `GET /foo` goes to third_handler. @@ -132,11 +132,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized { /// ```rust /// use tower_web::{body::BoxBody, routing::BoxRoute, prelude::*}; /// - /// async fn first_handler(request: Request) { /* ... */ } + /// async fn first_handler() { /* ... */ } /// - /// async fn second_handler(request: Request) { /* ... */ } + /// async fn second_handler() { /* ... */ } /// - /// async fn third_handler(request: Request) { /* ... */ } + /// async fn third_handler() { /* ... */ } /// /// fn app() -> BoxRoute { /// route("/", get(first_handler).post(second_handler)) @@ -181,11 +181,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized { /// use tower_web::prelude::*; /// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit}; /// - /// async fn first_handler(request: Request) { /* ... */ } + /// async fn first_handler() { /* ... */ } /// - /// async fn second_handler(request: Request) { /* ... */ } + /// async fn second_handler() { /* ... */ } /// - /// async fn third_handler(request: Request) { /* ... */ } + /// async fn third_handler() { /* ... */ } /// /// // All requests to `handler` and `other_handler` will be sent through /// // `ConcurrencyLimit` @@ -207,11 +207,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized { /// use tower_web::prelude::*; /// use tower_http::trace::TraceLayer; /// - /// async fn first_handler(request: Request) { /* ... */ } + /// async fn first_handler() { /* ... */ } /// - /// async fn second_handler(request: Request) { /* ... */ } + /// async fn second_handler() { /* ... */ } /// - /// async fn third_handler(request: Request) { /* ... */ } + /// async fn third_handler() { /* ... */ } /// /// let app = route("/", get(first_handler)) /// .route("/foo", get(second_handler)) @@ -584,7 +584,7 @@ impl Layered { /// use tower::{BoxError, timeout::TimeoutLayer}; /// use std::time::Duration; /// - /// async fn handler(request: Request) { /* ... */ } + /// async fn handler() { /* ... */ } /// /// // `Timeout` will fail with `BoxError` if the timeout elapses... /// let layered_handler = route("/", get(handler)) @@ -646,16 +646,17 @@ where /// /// ``` /// use tower_web::{routing::nest, prelude::*}; +/// use http::Uri; /// -/// async fn users_get(request: Request) { +/// async fn users_get(uri: Uri) { /// // `users_get` doesn't see the whole URL. `nest` will strip the matching /// // `/api` prefix. -/// assert_eq!(request.uri().path(), "/users"); +/// assert_eq!(uri.path(), "/users"); /// } /// -/// async fn users_post(request: Request) {} +/// async fn users_post() {} /// -/// async fn careers(request: Request) {} +/// async fn careers() {} /// /// let users_api = route("/users", get(users_get).post(users_post)); /// @@ -671,7 +672,7 @@ where /// ``` /// use tower_web::{routing::nest, prelude::*}; /// -/// async fn users_get(request: Request, params: extract::UrlParamsMap) { +/// async fn users_get(params: extract::UrlParamsMap) { /// // Both `version` and `id` were captured even though `users_api` only /// // explicitly captures `id`. /// let version = params.get("version"); diff --git a/src/service/mod.rs b/src/service/mod.rs index a0ba2080..df393db6 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -63,7 +63,7 @@ //! # let some_backpressure_sensitive_middleware = //! # tower::layer::util::Identity::new(); //! -//! async fn handler(request: Request) { /* ... */ } +//! async fn handler() { /* ... */ } //! //! let app = route("/", get(handler)); //! diff --git a/src/tests.rs b/src/tests.rs index cb776da1..fbb3c973 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -54,7 +54,7 @@ async fn hello_world() { #[tokio::test] async fn consume_body() { - let app = route("/", get(|_: Request, body: String| async { body })); + let app = route("/", get(|body: String| async { body })); let addr = run_in_background(app).await; @@ -79,7 +79,7 @@ async fn deserialize_body() { let app = route( "/", - post(|_: Request, input: extract::Json| async { input.0.foo }), + post(|input: extract::Json| async { input.0.foo }), ); let addr = run_in_background(app).await; @@ -137,11 +137,7 @@ async fn body_with_length_limit() { let app = route( "/", - post( - |req: Request, _body: extract::ContentLengthLimit| async move { - dbg!(&req); - }, - ), + post(|_body: extract::ContentLengthLimit| async {}), ); let addr = run_in_background(app).await; @@ -240,24 +236,20 @@ async fn routing() { async fn extracting_url_params() { let app = route( "/users/:id", - get( - |_: Request, params: extract::UrlParams<(i32,)>| async move { - let (id,) = params.0; - assert_eq!(id, 42); - }, - ) - .post( - |_: Request, params_map: extract::UrlParamsMap| async move { - assert_eq!(params_map.get("id").unwrap(), "1337"); - assert_eq!( - params_map - .get_typed::("id") - .expect("missing") - .expect("failed to parse"), - 1337 - ); - }, - ), + get(|params: extract::UrlParams<(i32,)>| async move { + let (id,) = params.0; + assert_eq!(id, 42); + }) + .post(|params_map: extract::UrlParamsMap| async move { + assert_eq!(params_map.get("id").unwrap(), "1337"); + assert_eq!( + params_map + .get_typed::("id") + .expect("missing") + .expect("failed to parse"), + 1337 + ); + }), ); let addr = run_in_background(app).await; @@ -502,35 +494,30 @@ async fn layer_on_whole_router() { async fn disjunction() { let api_routes = route( "/users", - get(|_: Request| async { "users#index" }) - .post(|_: Request| async { "users#create" }), + get(|| async { "users#index" }).post(|| async { "users#create" }), ) .route( "/users/:id", - get( - |_: Request, params: extract::UrlParamsMap| async move { - format!( - "{}: users#show ({})", - params.get("version").unwrap(), - params.get("id").unwrap() - ) - }, - ), + get(|params: extract::UrlParamsMap| async move { + format!( + "{}: users#show ({})", + params.get("version").unwrap(), + params.get("id").unwrap() + ) + }), ) .route( "/games/:id", - get( - |_: Request, params: extract::UrlParamsMap| async move { - format!( - "{}: games#show ({})", - params.get("version").unwrap(), - params.get("id").unwrap() - ) - }, - ), + get(|params: extract::UrlParamsMap| async move { + format!( + "{}: games#show ({})", + params.get("version").unwrap(), + params.get("id").unwrap() + ) + }), ); - let app = route("/", get(|_: Request| async { "hi" })).nest("/:version/api", api_routes); + let app = route("/", get(|| async { "hi" })).nest("/:version/api", api_routes); let addr = run_in_background(app).await;