mirror of
https://github.com/tokio-rs/axum.git
synced 2025-01-11 12:31:25 +01:00
Make Request<Body>
an extractor
This commit is contained in:
parent
90c3e5ba74
commit
c91dc7ce29
10 changed files with 268 additions and 200 deletions
81
README.md
81
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<Body>| 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<Body>) {
|
||||
async fn get_slash() {
|
||||
// `GET /` called
|
||||
}
|
||||
|
||||
async fn post_slash(req: Request<Body>) {
|
||||
async fn post_slash() {
|
||||
// `POST /` called
|
||||
}
|
||||
|
||||
async fn get_foo(req: Request<Body>) {
|
||||
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<Body>) -> &'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<Body>) -> 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<Body>) -> Vec<u8> {
|
||||
async fn bytes() -> Vec<u8> {
|
||||
vec![1, 2, 3, 4]
|
||||
}
|
||||
|
||||
// `()` gives an empty response
|
||||
async fn empty(req: Request<Body>) {}
|
||||
async fn empty() {}
|
||||
|
||||
// `StatusCode` gives an empty response with that status code
|
||||
async fn empty_with_status(req: Request<Body>) -> 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<Body>) -> (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<Body>) -> Html<&'static str> {
|
||||
async fn html() -> Html<&'static str> {
|
||||
Html("<h1>Hello, World!</h1>")
|
||||
}
|
||||
|
||||
// `Json` gives a content-type of `application/json` and works with any type
|
||||
// that implements `serde::Serialize`
|
||||
async fn json(req: Request<Body>) -> Json<Value> {
|
||||
async fn json() -> Json<Value> {
|
||||
Json(json!({ "data": 42 }))
|
||||
}
|
||||
|
||||
// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
||||
// returning errors
|
||||
async fn result(req: Request<Body>) -> Result<&'static str, StatusCode> {
|
||||
async fn result() -> Result<&'static str, StatusCode> {
|
||||
Ok("all good")
|
||||
}
|
||||
|
||||
// `Response` gives full control
|
||||
async fn response(req: Request<Body>) -> Response<Body> {
|
||||
async fn response() -> Response<Body> {
|
||||
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<Body>` 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<Body>, payload: extract::Json<CreateUser>) {
|
||||
async fn create_user(payload: extract::Json<CreateUser>) {
|
||||
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<Body>, 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<Body>,
|
||||
params: extract::UrlParams<(Uuid,)>,
|
||||
pagination: Option<extract::Query<Pagination>>,
|
||||
) {
|
||||
|
@ -228,6 +224,21 @@ async fn get_user_things(
|
|||
}
|
||||
```
|
||||
|
||||
Additionally `Request<Body>` is itself an extractor:
|
||||
|
||||
```rust
|
||||
use tower_web::prelude::*;
|
||||
|
||||
let app = route("/users/:id", post(handler));
|
||||
|
||||
async fn handler(req: Request<Body>) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
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<Body>) {}
|
||||
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<Body>) {}
|
||||
async fn get_slash() {}
|
||||
|
||||
async fn post_foo(req: Request<Body>) {}
|
||||
async fn post_foo() {}
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
@ -314,7 +325,7 @@ let app = route(
|
|||
})),
|
||||
);
|
||||
|
||||
async fn handle(req: Request<Body>) {}
|
||||
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<Body>) {}
|
||||
async fn handle() {}
|
||||
|
||||
async fn other_handle(req: Request<Body>) {}
|
||||
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<Body>,
|
||||
state: extract::Extension<Arc<State>>,
|
||||
) {
|
||||
let state: Arc<State> = 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
|
||||
|
||||
|
|
|
@ -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<Body>) -> response::Html<&'static str> {
|
||||
async fn handler() -> response::Html<&'static str> {
|
||||
response::Html("<h1>Hello, World!</h1>")
|
||||
}
|
||||
|
||||
async fn greet(_req: Request<Body>, params: extract::UrlParamsMap) -> Result<String, StatusCode> {
|
||||
async fn greet(params: extract::UrlParamsMap) -> Result<String, StatusCode> {
|
||||
if let Some(name) = params.get("name") {
|
||||
Ok(format!("Hello {}!", name))
|
||||
} else {
|
||||
|
|
|
@ -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<Body>,
|
||||
UrlParams((key,)): UrlParams<(String,)>,
|
||||
Extension(state): Extension<SharedState>,
|
||||
) -> Result<Bytes, StatusCode> {
|
||||
|
@ -86,7 +85,6 @@ async fn kv_get(
|
|||
}
|
||||
|
||||
async fn kv_set(
|
||||
_req: Request<Body>,
|
||||
UrlParams((key,)): UrlParams<(String,)>,
|
||||
ContentLengthLimit(bytes): ContentLengthLimit<Bytes, { 1024 * 5_000 }>, // ~5mb
|
||||
Extension(state): Extension<SharedState>,
|
||||
|
@ -94,7 +92,7 @@ async fn kv_set(
|
|||
state.write().unwrap().db.insert(key, bytes);
|
||||
}
|
||||
|
||||
async fn list_keys(_req: Request<Body>, Extension(state): Extension<SharedState>) -> String {
|
||||
async fn list_keys(Extension(state): Extension<SharedState>) -> String {
|
||||
let db = &state.read().unwrap().db;
|
||||
|
||||
db.keys()
|
||||
|
@ -104,12 +102,11 @@ async fn list_keys(_req: Request<Body>, Extension(state): Extension<SharedState>
|
|||
}
|
||||
|
||||
fn admin_routes() -> BoxRoute<BoxBody> {
|
||||
async fn delete_all_keys(_req: Request<Body>, Extension(state): Extension<SharedState>) {
|
||||
async fn delete_all_keys(Extension(state): Extension<SharedState>) {
|
||||
state.write().unwrap().db.clear();
|
||||
}
|
||||
|
||||
async fn remove_key(
|
||||
_req: Request<Body>,
|
||||
UrlParams((key,)): UrlParams<(String,)>,
|
||||
Extension(state): Extension<SharedState>,
|
||||
) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! 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.
|
||||
//! 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<Body>, payload: extract::Json<CreateUser>) {
|
||||
//! async fn create_user(payload: extract::Json<CreateUser>) {
|
||||
//! let payload: CreateUser = payload.0;
|
||||
//!
|
||||
//! // ...
|
||||
|
@ -52,7 +52,7 @@
|
|||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! async fn handler(req: Request<Body>, 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<Body>,
|
||||
//! // 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<Body>, payload: Option<Json<Value>>) {
|
||||
//! async fn create_user(payload: Option<Json<Value>>) {
|
||||
//! 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<Body>, Json(value): Json<Value>) {
|
||||
//! async fn create_user(Json(value): Json<Value>) {
|
||||
//! // `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<Body>, pagination: extract::Query<Pagination>) {
|
||||
/// async fn list_things(pagination: extract::Query<Pagination>) {
|
||||
/// let pagination: Pagination = pagination.0;
|
||||
///
|
||||
/// // ...
|
||||
|
@ -231,7 +230,7 @@ where
|
|||
/// password: String,
|
||||
/// }
|
||||
///
|
||||
/// async fn create_user(req: Request<Body>, payload: extract::Json<CreateUser>) {
|
||||
/// async fn create_user(payload: extract::Json<CreateUser>) {
|
||||
/// let payload: CreateUser = payload.0;
|
||||
///
|
||||
/// // ...
|
||||
|
@ -307,7 +306,7 @@ fn has_content_type<B>(req: &Request<B>, expected_content_type: &str) -> bool {
|
|||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// async fn handler(req: Request<Body>, state: extract::Extension<Arc<State>>) {
|
||||
/// async fn handler(state: extract::Extension<Arc<State>>) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
|
@ -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<Body>) -> Result<Self, Self::Rejection> {
|
||||
take_body(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FromRequest for Request<Body> {
|
||||
type Rejection = RequestAlreadyExtracted;
|
||||
|
||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||
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<Body>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.method().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FromRequest for Uri {
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.uri().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FromRequest for Version {
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.version())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FromRequest for HeaderMap {
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut Request<Body>) -> Result<Self, Self::Rejection> {
|
||||
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>, body: extract::ContentLengthLimit<String, 1024>) {
|
||||
/// async fn handler(body: extract::ContentLengthLimit<String, 1024>) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
|
@ -442,7 +496,7 @@ where
|
|||
/// ```rust,no_run
|
||||
/// use tower_web::prelude::*;
|
||||
///
|
||||
/// async fn users_show(req: Request<Body>, 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<Body>,
|
||||
/// 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<Body>) -> Result<Body, BodyAlreadyTaken> {
|
||||
struct BodyAlreadyTakenExt;
|
||||
fn take_body(req: &mut Request<Body>) -> Result<Body, BodyAlreadyExtracted> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Body>` 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
|
||||
|
|
|
@ -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<tower_web::body::Body>` 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<Body>` as the first argument and must return
|
||||
//! // something that implements `IntoResponse`, which `()` does
|
||||
//! async fn unit_handler(request: Request<Body>) {}
|
||||
//! // Handler that immediately returns an empty `200 OK` response.
|
||||
//! async fn unit_handler() {}
|
||||
//!
|
||||
//! // `String` also implements `IntoResponse`
|
||||
//! async fn string_handler(request: Request<Body>) -> 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>, body: Bytes) -> Result<String, StatusCode> {
|
||||
//! async fn buffer_body(body: Bytes) -> Result<String, StatusCode> {
|
||||
//! 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<Body>) {}
|
||||
/// 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<Body>) {}
|
||||
/// 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<Body>) {}
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// // Requests to `POST /` will go to `handler`.
|
||||
/// let app = route("/", on(MethodFilter::Post, handler));
|
||||
|
@ -250,7 +246,7 @@ pub trait Handler<In>: Sized {
|
|||
/// use tower_web::prelude::*;
|
||||
/// use tower::limit::{ConcurrencyLimitLayer, ConcurrencyLimit};
|
||||
///
|
||||
/// async fn handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn handler() { /* ... */ }
|
||||
///
|
||||
/// let layered_handler = handler.layer(ConcurrencyLimitLayer::new(64));
|
||||
/// ```
|
||||
|
@ -273,14 +269,14 @@ pub trait Handler<In>: Sized {
|
|||
#[async_trait]
|
||||
impl<F, Fut, Res> Handler<()> for F
|
||||
where
|
||||
F: FnOnce(Request<Body>) -> Fut + Send + Sync,
|
||||
F: FnOnce() -> Fut + Send + Sync,
|
||||
Fut: Future<Output = Res> + Send,
|
||||
Res: IntoResponse,
|
||||
{
|
||||
type Sealed = sealed::Hidden;
|
||||
|
||||
async fn call(self, req: Request<Body>) -> Response<BoxBody> {
|
||||
self(req).await.into_response().map(BoxBody::new)
|
||||
async fn call(self, _req: Request<Body>) -> Response<BoxBody> {
|
||||
self().await.into_response().map(BoxBody::new)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,7 +288,7 @@ macro_rules! impl_handler {
|
|||
#[allow(non_snake_case)]
|
||||
impl<F, Fut, Res, $head, $($tail,)*> Handler<($head, $($tail,)*)> for F
|
||||
where
|
||||
F: FnOnce(Request<Body>, $head, $($tail,)*) -> Fut + Send + Sync,
|
||||
F: FnOnce($head, $($tail,)*) -> Fut + Send + Sync,
|
||||
Fut: Future<Output = Res> + 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<S, T> Layered<S, T> {
|
|||
/// use tower::{BoxError, timeout::TimeoutLayer};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// async fn handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn handler() { /* ... */ }
|
||||
///
|
||||
/// // `Timeout` will fail with `BoxError` if the timeout elapses...
|
||||
/// let layered_handler = handler
|
||||
|
@ -545,9 +541,9 @@ impl<S, F> OnMethod<S, F> {
|
|||
/// ```rust
|
||||
/// use tower_web::prelude::*;
|
||||
///
|
||||
/// async fn handler(request: Request<Body>) {}
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// async fn other_handler(request: Request<Body>) {}
|
||||
/// async fn other_handler() {}
|
||||
///
|
||||
/// // Requests to `GET /` will go to `handler` and `POST /` will go to
|
||||
/// // `other_handler`.
|
||||
|
@ -628,9 +624,9 @@ impl<S, F> OnMethod<S, F> {
|
|||
/// ```rust
|
||||
/// use tower_web::{routing::MethodFilter, prelude::*};
|
||||
///
|
||||
/// async fn handler(request: Request<Body>) {}
|
||||
/// async fn handler() {}
|
||||
///
|
||||
/// async fn other_handler(request: Request<Body>) {}
|
||||
/// async fn other_handler() {}
|
||||
///
|
||||
/// // Requests to `GET /` will go to `handler` and `DELETE /` will go to
|
||||
/// // `other_handler`
|
||||
|
|
81
src/lib.rs
81
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<Body>| 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<Body>) {
|
||||
//! async fn get_slash() {
|
||||
//! // `GET /` called
|
||||
//! }
|
||||
//!
|
||||
//! async fn post_slash(req: Request<Body>) {
|
||||
//! async fn post_slash() {
|
||||
//! // `POST /` called
|
||||
//! }
|
||||
//!
|
||||
//! async fn get_foo(req: Request<Body>) {
|
||||
//! 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<Body>) -> &'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<Body>) -> 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<Body>) -> Vec<u8> {
|
||||
//! async fn bytes() -> Vec<u8> {
|
||||
//! vec![1, 2, 3, 4]
|
||||
//! }
|
||||
//!
|
||||
//! // `()` gives an empty response
|
||||
//! async fn empty(req: Request<Body>) {}
|
||||
//! async fn empty() {}
|
||||
//!
|
||||
//! // `StatusCode` gives an empty response with that status code
|
||||
//! async fn empty_with_status(req: Request<Body>) -> 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<Body>) -> (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<Body>) -> Html<&'static str> {
|
||||
//! async fn html() -> Html<&'static str> {
|
||||
//! Html("<h1>Hello, World!</h1>")
|
||||
//! }
|
||||
//!
|
||||
//! // `Json` gives a content-type of `application/json` and works with any type
|
||||
//! // that implements `serde::Serialize`
|
||||
//! async fn json(req: Request<Body>) -> Json<Value> {
|
||||
//! async fn json() -> Json<Value> {
|
||||
//! Json(json!({ "data": 42 }))
|
||||
//! }
|
||||
//!
|
||||
//! // `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
||||
//! // returning errors
|
||||
//! async fn result(req: Request<Body>) -> Result<&'static str, StatusCode> {
|
||||
//! async fn result() -> Result<&'static str, StatusCode> {
|
||||
//! Ok("all good")
|
||||
//! }
|
||||
//!
|
||||
//! // `Response` gives full control
|
||||
//! async fn response(req: Request<Body>) -> Response<Body> {
|
||||
//! async fn response() -> Response<Body> {
|
||||
//! Response::builder().body(Body::empty()).unwrap()
|
||||
//! }
|
||||
//!
|
||||
|
@ -152,13 +150,12 @@
|
|||
//!
|
||||
//! # 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`](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<Body>, payload: extract::Json<CreateUser>) {
|
||||
//! async fn create_user(payload: extract::Json<CreateUser>) {
|
||||
//! let payload: CreateUser = payload.0;
|
||||
//!
|
||||
//! // ...
|
||||
|
@ -192,7 +189,7 @@
|
|||
//!
|
||||
//! let app = route("/users/:id", post(create_user));
|
||||
//!
|
||||
//! async fn create_user(req: Request<Body>, 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<Body>,
|
||||
//! params: extract::UrlParams<(Uuid,)>,
|
||||
//! pagination: Option<extract::Query<Pagination>>,
|
||||
//! ) {
|
||||
|
@ -241,6 +237,24 @@
|
|||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! Additionally `Request<Body>` is itself an extractor:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use tower_web::prelude::*;
|
||||
//!
|
||||
//! let app = route("/users/:id", post(handler));
|
||||
//!
|
||||
//! async fn handler(req: Request<Body>) {
|
||||
//! // ...
|
||||
//! }
|
||||
//! # 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<Body>) {}
|
||||
//! 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<Body>) {}
|
||||
//! async fn get_slash() {}
|
||||
//!
|
||||
//! async fn post_foo(req: Request<Body>) {}
|
||||
//! 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<Body>) {}
|
||||
//! 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<Body>) {}
|
||||
//! async fn handle() {}
|
||||
//!
|
||||
//! async fn other_handle(req: Request<Body>) {}
|
||||
//! 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<Body>,
|
||||
//! state: extract::Extension<Arc<State>>,
|
||||
//! ) {
|
||||
//! let state: Arc<State> = state.0;
|
||||
|
|
|
@ -88,11 +88,11 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
|
|||
/// ```rust
|
||||
/// use tower_web::prelude::*;
|
||||
///
|
||||
/// async fn first_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn first_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn second_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
||||
/// 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<Body>) { /* ... */ }
|
||||
/// async fn first_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn second_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn third_handler() { /* ... */ }
|
||||
///
|
||||
/// fn app() -> BoxRoute<BoxBody> {
|
||||
/// 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<Body>) { /* ... */ }
|
||||
/// async fn first_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn second_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
||||
/// 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<Body>) { /* ... */ }
|
||||
/// async fn first_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn second_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn second_handler() { /* ... */ }
|
||||
///
|
||||
/// async fn third_handler(request: Request<Body>) { /* ... */ }
|
||||
/// async fn third_handler() { /* ... */ }
|
||||
///
|
||||
/// let app = route("/", get(first_handler))
|
||||
/// .route("/foo", get(second_handler))
|
||||
|
@ -584,7 +584,7 @@ impl<S> Layered<S> {
|
|||
/// use tower::{BoxError, timeout::TimeoutLayer};
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// async fn handler(request: Request<Body>) { /* ... */ }
|
||||
/// 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<Body>) {
|
||||
/// 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<Body>) {}
|
||||
/// async fn users_post() {}
|
||||
///
|
||||
/// async fn careers(request: Request<Body>) {}
|
||||
/// 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<Body>, 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");
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
//! # let some_backpressure_sensitive_middleware =
|
||||
//! # tower::layer::util::Identity::new();
|
||||
//!
|
||||
//! async fn handler(request: Request<Body>) { /* ... */ }
|
||||
//! async fn handler() { /* ... */ }
|
||||
//!
|
||||
//! let app = route("/", get(handler));
|
||||
//!
|
||||
|
|
79
src/tests.rs
79
src/tests.rs
|
@ -54,7 +54,7 @@ async fn hello_world() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn consume_body() {
|
||||
let app = route("/", get(|_: Request<Body>, 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<Body>, input: extract::Json<Input>| async { input.0.foo }),
|
||||
post(|input: extract::Json<Input>| 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>, _body: extract::ContentLengthLimit<Bytes, LIMIT>| async move {
|
||||
dbg!(&req);
|
||||
},
|
||||
),
|
||||
post(|_body: extract::ContentLengthLimit<Bytes, LIMIT>| 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<Body>, params: extract::UrlParams<(i32,)>| async move {
|
||||
let (id,) = params.0;
|
||||
assert_eq!(id, 42);
|
||||
},
|
||||
)
|
||||
.post(
|
||||
|_: Request<Body>, params_map: extract::UrlParamsMap| async move {
|
||||
assert_eq!(params_map.get("id").unwrap(), "1337");
|
||||
assert_eq!(
|
||||
params_map
|
||||
.get_typed::<i32>("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::<i32>("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<Body>| async { "users#index" })
|
||||
.post(|_: Request<Body>| async { "users#create" }),
|
||||
get(|| async { "users#index" }).post(|| async { "users#create" }),
|
||||
)
|
||||
.route(
|
||||
"/users/:id",
|
||||
get(
|
||||
|_: Request<Body>, 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<Body>, 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<Body>| 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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue