Accept `S::Response: IntoResponse` everywhere (#1165)

This commit is contained in:
David Pedersen 2022-07-19 18:55:15 +02:00 committed by GitHub
parent 643909af6d
commit b243e171fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 96 additions and 99 deletions

View File

@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning].
- **added:** Add `RouterExt::route_with_tsr` for adding routes with an
additional "trailing slash redirect" route ([#1119])
- **changed:** For methods that accept some `S: Service`, the bounds have been
relaxed so the response type must implement `IntoResponse` rather than being a
literal `Response`
[#1119]: https://github.com/tokio-rs/axum/pull/1119

View File

@ -3,7 +3,7 @@
use axum::{
handler::Handler,
http::Request,
response::{Redirect, Response},
response::{IntoResponse, Redirect},
Router,
};
use std::{convert::Infallible, future::ready};
@ -161,7 +161,8 @@ pub trait RouterExt<B>: sealed::Sealed {
/// ```
fn route_with_tsr<T>(self, path: &str, service: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
Self: Sized;
}
@ -252,7 +253,8 @@ where
fn route_with_tsr<T>(mut self, path: &str, service: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
Self: Sized,
{

View File

@ -2,7 +2,7 @@ use axum::{
body::Body,
handler::Handler,
http::Request,
response::Response,
response::IntoResponse,
routing::{delete, get, on, post, MethodFilter},
Router,
};
@ -141,7 +141,8 @@ where
/// The routes will be nested at `/{resource_name}/:{resource_name}_id`.
pub fn nest<T>(mut self, svc: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
let path = self.show_update_destroy_path();
@ -154,7 +155,8 @@ where
/// The routes will be nested at `/{resource_name}`.
pub fn nest_collection<T>(mut self, svc: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
let path = self.index_create_path();
@ -172,7 +174,8 @@ where
fn route<T>(mut self, path: &str, svc: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
self.router = self.router.route(path, svc);

View File

@ -1,7 +1,7 @@
use axum::{
body::{Body, HttpBody},
error_handling::HandleError,
response::Response,
response::IntoResponse,
routing::{get_service, Route},
Router,
};
@ -150,8 +150,8 @@ impl<B, T, F> SpaRouter<B, T, F> {
impl<B, F, T> From<SpaRouter<B, T, F>> for Router<B>
where
F: Clone + Send + 'static,
HandleError<Route<B, io::Error>, F, T>:
Service<Request<B>, Response = Response, Error = Infallible>,
HandleError<Route<B, io::Error>, F, T>: Service<Request<B>, Error = Infallible>,
<HandleError<Route<B, io::Error>, F, T> as Service<Request<B>>>::Response: IntoResponse + Send,
<HandleError<Route<B, io::Error>, F, T> as Service<Request<B>>>::Future: Send,
B: HttpBody + Send + 'static,
T: 'static,

View File

@ -29,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **added:** Support any middleware response that implements `IntoResponse` ([#1152])
- **breaking:** Require middleware added with `Handler::layer` to have
`Infallible` as the error type ([#1152])
- **changed:** For methods that accept some `S: Service`, the bounds have been
relaxed so the response type must implement `IntoResponse` rather than being a
literal `Response`
[#1171]: https://github.com/tokio-rs/axum/pull/1171
[#1077]: https://github.com/tokio-rs/axum/pull/1077

View File

@ -1,11 +1,10 @@
#![doc = include_str!("../docs/error_handling.md")]
use crate::{
body::{boxed, Bytes, HttpBody},
body::boxed,
extract::{FromRequest, RequestParts},
http::{Request, StatusCode},
response::{IntoResponse, Response},
BoxError,
};
use std::{
convert::Infallible,
@ -114,17 +113,16 @@ where
}
}
impl<S, F, ReqBody, ResBody, Fut, Res> Service<Request<ReqBody>> for HandleError<S, F, ()>
impl<S, F, ReqBody, Fut, Res> Service<Request<ReqBody>> for HandleError<S, F, ()>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
S: Service<Request<ReqBody>> + Clone + Send + 'static,
S::Response: IntoResponse + Send,
S::Error: Send,
S::Future: Send,
F: FnOnce(S::Error) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
ReqBody: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
type Response = Response;
type Error = Infallible;
@ -142,7 +140,7 @@ where
let future = Box::pin(async move {
match inner.oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Ok(res) => Ok(res.into_response()),
Err(err) => Ok(f(err).await.into_response()),
}
});
@ -154,10 +152,11 @@ where
#[allow(unused_macros)]
macro_rules! impl_service {
( $($ty:ident),* $(,)? ) => {
impl<S, F, ReqBody, ResBody, Res, Fut, $($ty,)*> Service<Request<ReqBody>>
impl<S, F, ReqBody, Res, Fut, $($ty,)*> Service<Request<ReqBody>>
for HandleError<S, F, ($($ty,)*)>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
S: Service<Request<ReqBody>> + Clone + Send + 'static,
S::Response: IntoResponse + Send,
S::Error: Send,
S::Future: Send,
F: FnOnce($($ty),*, S::Error) -> Fut + Clone + Send + 'static,
@ -165,8 +164,6 @@ macro_rules! impl_service {
Res: IntoResponse,
$( $ty: FromRequest<ReqBody> + Send,)*
ReqBody: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
type Response = Response;
type Error = Infallible;
@ -202,7 +199,7 @@ macro_rules! impl_service {
};
match inner.oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Ok(res) => Ok(res.into_response()),
Err(err) => Ok(f($($ty),*, err).await.into_response().map(boxed)),
}
});

View File

@ -1,8 +1,6 @@
use crate::{
body::{Bytes, HttpBody},
extract::{FromRequest, RequestParts},
response::{IntoResponse, Response},
BoxError,
};
use futures_util::{future::BoxFuture, ready};
use http::Request;
@ -90,6 +88,8 @@ use tower_service::Service;
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// [`Bytes`]: bytes::Bytes
pub fn from_extractor<E>() -> FromExtractorLayer<E> {
FromExtractorLayer(PhantomData)
}
@ -166,13 +166,12 @@ where
}
}
impl<S, E, ReqBody, ResBody> Service<Request<ReqBody>> for FromExtractor<S, E>
impl<S, E, ReqBody> Service<Request<ReqBody>> for FromExtractor<S, E>
where
E: FromRequest<ReqBody> + 'static,
ReqBody: Default + Send + 'static,
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
S: Service<Request<ReqBody>> + Clone,
S::Response: IntoResponse,
{
type Response = Response;
type Error = S::Error;
@ -225,13 +224,12 @@ pin_project! {
}
}
impl<ReqBody, S, E, ResBody> Future for ResponseFuture<ReqBody, S, E>
impl<ReqBody, S, E> Future for ResponseFuture<ReqBody, S, E>
where
E: FromRequest<ReqBody>,
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
S: Service<Request<ReqBody>>,
S::Response: IntoResponse,
ReqBody: Default,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
type Output = Result<Response, S::Error>;
@ -259,7 +257,7 @@ where
StateProj::Call { future } => {
return future
.poll(cx)
.map(|result| result.map(|response| response.map(crate::body::boxed)));
.map(|result| result.map(IntoResponse::into_response));
}
};

View File

@ -1,8 +1,4 @@
use crate::{
body::{self, Bytes, HttpBody},
response::{IntoResponse, Response},
BoxError,
};
use crate::response::{IntoResponse, Response};
use axum_core::extract::{FromRequest, RequestParts};
use futures_util::future::BoxFuture;
use http::Request;
@ -16,7 +12,6 @@ use std::{
task::{Context, Poll},
};
use tower::{util::BoxCloneService, ServiceBuilder};
use tower_http::ServiceBuilderExt;
use tower_layer::Layer;
use tower_service::Service;
@ -256,20 +251,19 @@ where
macro_rules! impl_service {
( $($ty:ident),* $(,)? ) => {
#[allow(non_snake_case)]
impl<F, Fut, Out, S, ReqBody, ResBody, $($ty,)*> Service<Request<ReqBody>> for FromFn<F, S, ($($ty,)*)>
impl<F, Fut, Out, S, ReqBody, $($ty,)*> Service<Request<ReqBody>> for FromFn<F, S, ($($ty,)*)>
where
F: FnMut($($ty),*, Next<ReqBody>) -> Fut + Clone + Send + 'static,
$( $ty: FromRequest<ReqBody> + Send, )*
Fut: Future<Output = Out> + Send + 'static,
Out: IntoResponse + 'static,
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible>
S: Service<Request<ReqBody>, Error = Infallible>
+ Clone
+ Send
+ 'static,
S::Response: IntoResponse,
S::Future: Send + 'static,
ReqBody: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
type Response = Response;
type Error = Infallible;
@ -296,7 +290,7 @@ macro_rules! impl_service {
let inner = ServiceBuilder::new()
.boxed_clone()
.map_response_body(body::boxed)
.map_response(IntoResponse::into_response)
.service(ready_inner);
let next = Next { inner };
@ -370,7 +364,7 @@ impl fmt::Debug for ResponseFuture {
#[cfg(test)]
mod tests {
use super::*;
use crate::{body::Empty, routing::get, Router};
use crate::{body::Body, routing::get, Router};
use http::{HeaderMap, StatusCode};
use tower::ServiceExt;
@ -392,12 +386,7 @@ mod tests {
.layer(from_fn(insert_header));
let res = app
.oneshot(
Request::builder()
.uri("/")
.body(body::boxed(Empty::new()))
.unwrap(),
)
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(res.status(), StatusCode::OK);

View File

@ -7,7 +7,6 @@ use crate::{
http::{Method, Request, StatusCode},
response::Response,
routing::{future::RouteFuture, Fallback, MethodFilter, Route},
BoxError,
};
use axum_core::response::IntoResponse;
use bytes::BytesMut;
@ -17,7 +16,7 @@ use std::{
marker::PhantomData,
task::{Context, Poll},
};
use tower::{service_fn, util::MapResponseLayer, ServiceBuilder, ServiceExt};
use tower::{service_fn, util::MapResponseLayer, ServiceBuilder};
use tower_layer::Layer;
use tower_service::Service;
@ -76,12 +75,11 @@ macro_rules! top_level_service_fn {
$name:ident, $method:ident
) => {
$(#[$m])+
pub fn $name<S, ReqBody, ResBody>(svc: S) -> MethodRouter<ReqBody, S::Error>
pub fn $name<S, ReqBody>(svc: S) -> MethodRouter<ReqBody, S::Error>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
S: Service<Request<ReqBody>> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
on_service(MethodFilter::$method, svc)
}
@ -208,15 +206,14 @@ macro_rules! chained_service_fn {
$name:ident, $method:ident
) => {
$(#[$m])+
pub fn $name<S, ResBody>(self, svc: S) -> Self
pub fn $name<S>(self, svc: S) -> Self
where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = E>
S: Service<Request<ReqBody>, Error = E>
+ Clone
+ Send
+ 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
self.on_service(MethodFilter::$method, svc)
}
@ -316,15 +313,11 @@ top_level_service_fn!(trace_service, TRACE);
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
pub fn on_service<S, ReqBody, ResBody>(
filter: MethodFilter,
svc: S,
) -> MethodRouter<ReqBody, S::Error>
pub fn on_service<S, ReqBody>(filter: MethodFilter, svc: S) -> MethodRouter<ReqBody, S::Error>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
S: Service<Request<ReqBody>> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
MethodRouter::new().on_service(filter, svc)
}
@ -382,12 +375,11 @@ where
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
pub fn any_service<S, ReqBody, ResBody>(svc: S) -> MethodRouter<ReqBody, S::Error>
pub fn any_service<S, ReqBody>(svc: S) -> MethodRouter<ReqBody, S::Error>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
S: Service<Request<ReqBody>> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
MethodRouter::new().fallback(svc).skip_allow_header()
}
@ -684,17 +676,13 @@ impl<ReqBody, E> MethodRouter<ReqBody, E> {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
pub fn on_service<S, ResBody>(self, filter: MethodFilter, svc: S) -> Self
pub fn on_service<S>(self, filter: MethodFilter, svc: S) -> Self
where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = E>
+ Clone
+ Send
+ 'static,
S: Service<Request<ReqBody>, Error = E> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
self.on_service_boxed_response_body(filter, svc.map_response(|res| res.map(boxed)))
self.on_service_boxed_response_body(filter, svc)
}
chained_service_fn!(delete_service, DELETE);
@ -707,23 +695,20 @@ impl<ReqBody, E> MethodRouter<ReqBody, E> {
chained_service_fn!(trace_service, TRACE);
#[doc = include_str!("../docs/method_routing/fallback.md")]
pub fn fallback<S, ResBody>(mut self, svc: S) -> Self
pub fn fallback<S>(mut self, svc: S) -> Self
where
S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = E>
+ Clone
+ Send
+ 'static,
S: Service<Request<ReqBody>, Error = E> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
ResBody: HttpBody<Data = Bytes> + Send + 'static,
ResBody::Error: Into<BoxError>,
{
self.fallback = Fallback::Custom(Route::new(svc.map_response(|res| res.map(boxed))));
self.fallback = Fallback::Custom(Route::new(svc));
self
}
fn fallback_boxed_response_body<S>(mut self, svc: S) -> Self
where
S: Service<Request<ReqBody>, Response = Response, Error = E> + Clone + Send + 'static,
S: Service<Request<ReqBody>, Error = E> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
{
self.fallback = Fallback::Custom(Route::new(svc));
@ -886,9 +871,10 @@ impl<ReqBody, E> MethodRouter<ReqBody, E> {
pub fn handle_error<F, T>(self, f: F) -> MethodRouter<ReqBody, Infallible>
where
F: Clone + Send + 'static,
HandleError<Route<ReqBody, E>, F, T>:
Service<Request<ReqBody>, Response = Response, Error = Infallible>,
HandleError<Route<ReqBody, E>, F, T>: Service<Request<ReqBody>, Error = Infallible>,
<HandleError<Route<ReqBody, E>, F, T> as Service<Request<ReqBody>>>::Future: Send,
<HandleError<Route<ReqBody, E>, F, T> as Service<Request<ReqBody>>>::Response:
IntoResponse + Send,
T: 'static,
E: 'static,
ReqBody: 'static,
@ -898,7 +884,8 @@ impl<ReqBody, E> MethodRouter<ReqBody, E> {
fn on_service_boxed_response_body<S>(self, filter: MethodFilter, svc: S) -> Self
where
S: Service<Request<ReqBody>, Response = Response, Error = E> + Clone + Send + 'static,
S: Service<Request<ReqBody>, Error = E> + Clone + Send + 'static,
S::Response: IntoResponse + 'static,
S::Future: Send + 'static,
{
macro_rules! set_service {
@ -1319,14 +1306,22 @@ mod tests {
async fn call<S>(method: Method, svc: &mut S) -> (StatusCode, HeaderMap, String)
where
S: Service<Request<Body>, Response = Response, Error = Infallible>,
S: Service<Request<Body>, Error = Infallible>,
S::Response: IntoResponse,
{
let request = Request::builder()
.uri("/")
.method(method)
.body(Body::empty())
.unwrap();
let response = svc.ready().await.unwrap().call(request).await.unwrap();
let response = svc
.ready()
.await
.unwrap()
.call(request)
.await
.unwrap()
.into_response();
let (parts, body) = response.into_parts();
let body = String::from_utf8(hyper::body::to_bytes(body).await.unwrap().to_vec()).unwrap();
(parts.status, parts.headers, body)

View File

@ -122,7 +122,8 @@ where
#[doc = include_str!("../docs/routing/route.md")]
pub fn route<T>(mut self, path: &str, service: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
if path.is_empty() {
@ -176,7 +177,8 @@ where
#[doc = include_str!("../docs/routing/nest.md")]
pub fn nest<T>(mut self, mut path: &str, svc: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
if path.is_empty() {
@ -368,7 +370,8 @@ where
#[doc = include_str!("../docs/routing/fallback.md")]
pub fn fallback<T>(mut self, svc: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
T::Response: IntoResponse,
T::Future: Send + 'static,
{
self.fallback = Fallback::Custom(Route::new(svc));

View File

@ -2,6 +2,7 @@ use crate::{
body::{boxed, Body, Empty, HttpBody},
response::Response,
};
use axum_core::response::IntoResponse;
use bytes::Bytes;
use http::{
header::{self, CONTENT_LENGTH},
@ -30,10 +31,13 @@ pub struct Route<B = Body, E = Infallible>(BoxCloneService<Request<B>, Response,
impl<B, E> Route<B, E> {
pub(super) fn new<T>(svc: T) -> Self
where
T: Service<Request<B>, Response = Response, Error = E> + Clone + Send + 'static,
T: Service<Request<B>, Error = E> + Clone + Send + 'static,
T::Response: IntoResponse + 'static,
T::Future: Send + 'static,
{
Self(BoxCloneService::new(svc))
Self(BoxCloneService::new(
svc.map_response(IntoResponse::into_response),
))
}
pub(crate) fn oneshot_inner(