use crate::Status; use http::Response; use pin_project::pin_project; use std::{ future::Future, pin::Pin, task::{ready, Context, Poll}, }; use tower::Service; /// Middleware that attempts to recover from service errors by turning them into a response built /// from the `Status`. #[derive(Debug, Clone)] pub(crate) struct RecoverError { inner: S, } impl RecoverError { pub(crate) fn new(inner: S) -> Self { Self { inner } } } impl Service for RecoverError where S: Service>, S::Error: Into, { type Response = Response>; type Error = crate::Error; type Future = ResponseFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.inner.poll_ready(cx).map_err(Into::into) } fn call(&mut self, req: R) -> Self::Future { ResponseFuture { inner: self.inner.call(req), } } } #[pin_project] pub(crate) struct ResponseFuture { #[pin] inner: F, } impl Future for ResponseFuture where F: Future, E>>, E: Into, { type Output = Result>, crate::Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let result: Result, crate::Error> = ready!(self.project().inner.poll(cx)).map_err(Into::into); match result { Ok(response) => { let response = response.map(MaybeEmptyBody::full); Poll::Ready(Ok(response)) } Err(err) => match Status::try_from_error(err) { Ok(status) => { let mut res = Response::new(MaybeEmptyBody::empty()); status.add_header(res.headers_mut()).unwrap(); Poll::Ready(Ok(res)) } Err(err) => Poll::Ready(Err(err)), }, } } } #[pin_project] pub(crate) struct MaybeEmptyBody { #[pin] inner: Option, } impl MaybeEmptyBody { fn full(inner: B) -> Self { Self { inner: Some(inner) } } fn empty() -> Self { Self { inner: None } } } impl http_body::Body for MaybeEmptyBody where B: http_body::Body + Send, { type Data = B::Data; type Error = B::Error; fn poll_data( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { match self.project().inner.as_pin_mut() { Some(b) => b.poll_data(cx), None => Poll::Ready(None), } } fn poll_trailers( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>> { match self.project().inner.as_pin_mut() { Some(b) => b.poll_trailers(cx), None => Poll::Ready(Ok(None)), } } fn is_end_stream(&self) -> bool { match &self.inner { Some(b) => b.is_end_stream(), None => true, } } }