Move methods from ServiceExt to RoutingDsl (#160)

Previously, on `main`, this wouldn't compile:

```rust
let app = route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .timeout(Duration::from_secs(10))
            .into_inner(),
    )
    .handle_error(...)
    .route(...); // <-- doesn't work
```

That is because `handle_error` would be
`axum::service::ServiceExt::handle_error` which returns `HandleError<_,
_, _, HandleErrorFromService>` which does _not_ implement `RoutingDsl`.
So you couldn't call `route`. This was caused by
https://github.com/tokio-rs/axum/pull/120.

Basically `handle_error` when called on a `RoutingDsl`, the resulting
service should also implement `RoutingDsl`, but if called on another
random service it should _not_ implement `RoutingDsl`.

I don't think thats possible by having `handle_error` on `ServiceExt`
which is implemented for any service, since all axum routers are also
services by design.

This resolves the issue by removing `ServiceExt` and moving its methods
to `RoutingDsl`. Then we have more tight control over what has a
`handle_error` method.

`service::OnMethod` now also has a `handle_error` so you can still
handle errors from random services, by doing
`service::any(svc).handle_error(...)`.
This commit is contained in:
David Pedersen
2021-08-08 14:30:51 +02:00
committed by GitHub
parent 9b3f3c9bdf
commit 8013165908
12 changed files with 346 additions and 297 deletions
+21 -71
View File
@@ -94,7 +94,6 @@ use crate::{
use bytes::Bytes;
use http::{Request, Response};
use std::{
convert::Infallible,
fmt,
marker::PhantomData,
task::{Context, Poll},
@@ -428,6 +427,26 @@ impl<S, F> OnMethod<S, F> {
fallback: self,
}
}
/// Handle errors this service might produce, by mapping them to responses.
///
/// Unhandled errors will close the connection without sending a response.
///
/// Works similarly to [`RoutingDsl::handle_error`]. See that for more
/// details.
///
/// [`RoutingDsl::handle_error`]: crate::routing::RoutingDsl::handle_error
pub fn handle_error<ReqBody, H, Res, E>(
self,
f: H,
) -> HandleError<Self, H, ReqBody, HandleErrorFromService>
where
Self: Service<Request<ReqBody>, Response = Response<BoxBody>>,
H: FnOnce(<Self as Service<Request<ReqBody>>>::Error) -> Result<Res, E>,
Res: IntoResponse,
{
HandleError::new(self, f)
}
}
// this is identical to `routing::OnMethod`'s implementation. Would be nice to find a way to clean
@@ -462,7 +481,7 @@ where
///
/// Created with
/// [`handler::Layered::handle_error`](crate::handler::Layered::handle_error) or
/// [`routing::Layered::handle_error`](crate::routing::Layered::handle_error).
/// [`routing::RoutingDsl::handle_error`](crate::routing::RoutingDsl::handle_error).
/// See those methods for more details.
pub struct HandleError<S, F, B, T> {
inner: S,
@@ -542,75 +561,6 @@ where
}
}
/// Extension trait that adds additional methods to [`Service`].
pub trait ServiceExt<ReqBody, ResBody>:
Service<Request<ReqBody>, Response = Response<ResBody>>
{
/// Handle errors from a service.
///
/// `handle_error` takes a closure that will map errors from the service
/// into responses. The closure's return type must be `Result<T, E>` where
/// `T` implements [`IntoResponse`](crate::response::IntoResponse).
///
/// # Example
///
/// ```rust,no_run
/// use axum::{service::{self, ServiceExt}, prelude::*};
/// use http::{Response, StatusCode};
/// use tower::{service_fn, BoxError};
/// use std::convert::Infallible;
///
/// // A service that might fail with `std::io::Error`
/// let service = service_fn(|_: Request<Body>| async {
/// let res = Response::new(Body::empty());
/// Ok::<_, std::io::Error>(res)
/// });
///
/// let app = route(
/// "/",
/// service.handle_error(|error: std::io::Error| {
/// Ok::<_, Infallible>((
/// StatusCode::INTERNAL_SERVER_ERROR,
/// error.to_string(),
/// ))
/// }),
/// );
/// #
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// It works similarly to [`routing::Layered::handle_error`]. See that for more details.
///
/// [`routing::Layered::handle_error`]: crate::routing::Layered::handle_error
fn handle_error<F, Res, E>(self, f: F) -> HandleError<Self, F, ReqBody, HandleErrorFromService>
where
Self: Sized,
F: FnOnce(Self::Error) -> Result<Res, E>,
Res: IntoResponse,
ResBody: http_body::Body<Data = Bytes> + Send + Sync + 'static,
ResBody::Error: Into<BoxError> + Send + Sync + 'static,
{
HandleError::new(self, f)
}
/// Check that your service cannot fail.
///
/// That is, its error type is [`Infallible`].
fn check_infallible(self) -> Self
where
Self: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Sized,
{
self
}
}
impl<S, ReqBody, ResBody> ServiceExt<ReqBody, ResBody> for S where
S: Service<Request<ReqBody>, Response = Response<ResBody>>
{
}
/// A [`Service`] that boxes response bodies.
pub struct BoxResponseBody<S, B> {
inner: S,