From 8d446f40f22e6772f1f9c4f4bdb99d806f6a9ac1 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 15 Nov 2021 14:16:50 +0100 Subject: [PATCH] Change compression body error type to `BoxError` (#166) * Change compression body error type to `BoxError` The compression related middleware would previously use `BodyOrIoError` as the body error type. That only implemented `std::error::Error` if the inner error did as well. Since `Box` does not implement `std::error::Error` `Compression` and `Decompression` wouldn't be usable with hyper if the body they wrapped had that error type. This changes the middleware to use `BoxError` as the error type which resolves the issue. Its also consistent with other middleware. Reimplementation of #107 on top of latest `master`. * changelog * fix doc tests --- tower-http/CHANGELOG.md | 8 ++++- tower-http/src/compression/body.rs | 9 ++--- tower-http/src/compression/layer.rs | 2 +- tower-http/src/compression/mod.rs | 4 +-- tower-http/src/compression_utils.rs | 12 +++---- tower-http/src/decompression/body.rs | 13 +++++--- tower-http/src/decompression/mod.rs | 6 ++-- tower-http/src/lib.rs | 49 ++-------------------------- 8 files changed, 35 insertions(+), 68 deletions(-) diff --git a/tower-http/CHANGELOG.md b/tower-http/CHANGELOG.md index ae168a19..20978d74 100644 --- a/tower-http/CHANGELOG.md +++ b/tower-http/CHANGELOG.md @@ -9,12 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ServeDir` and `ServeFile`: Ability to serve precompressed files ([#156]) - `Trace`: Add `DefaultMakeSpan::level` to make log level of tracing spans easily configurable ([#124]) +- Change the response body error type of `Compression` and `Decompression` to + `Box`. This makes them usable if the body + they're wrapping uses `Box` as its error + type which they previously weren't ([#166]) +- Remove `BodyOrIoError`. Its been replaced with `Box` ([#166]) - `SetRequestHeaderLayer`, `SetResponseHeaderLayer`: Remove unnecessary generic parameter ([#148]) - This removes the need (and possibility) to specify a body type for these layers. [#124]: https://github.com/tower-rs/tower-http/pull/124 [#156]: https://github.com/tower-rs/tower-http/pull/156 +[#166]: https://github.com/tower-rs/tower-http/pull/166 [#148]: https://github.com/tower-rs/tower-http/pull/148 # 0.1.2 (November 13, 2021) diff --git a/tower-http/src/compression/body.rs b/tower-http/src/compression/body.rs index 63666640..cb0f02c1 100644 --- a/tower-http/src/compression/body.rs +++ b/tower-http/src/compression/body.rs @@ -2,7 +2,7 @@ use crate::{ compression_utils::{AsyncReadBody, BodyIntoStream, DecorateAsyncRead, WrapBody}, - BodyOrIoError, + BoxError, }; #[cfg(feature = "compression-br")] use async_compression::tokio::bufread::BrotliEncoder; @@ -137,9 +137,10 @@ where impl Body for CompressionBody where B: Body, + B::Error: Into, { type Data = Bytes; - type Error = BodyOrIoError; + type Error = BoxError; fn poll_data( self: Pin<&mut Self>, @@ -157,7 +158,7 @@ where let bytes = buf.copy_to_bytes(buf.remaining()); Poll::Ready(Some(Ok(bytes))) } - Some(Err(err)) => Poll::Ready(Some(Err(BodyOrIoError::Body(err)))), + Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), None => Poll::Ready(None), }, } @@ -174,7 +175,7 @@ where BodyInnerProj::Deflate(inner) => inner.poll_trailers(cx), #[cfg(feature = "compression-br")] BodyInnerProj::Brotli(inner) => inner.poll_trailers(cx), - BodyInnerProj::Identity(body) => body.poll_trailers(cx).map_err(BodyOrIoError::Body), + BodyInnerProj::Identity(body) => body.poll_trailers(cx).map_err(Into::into), } } } diff --git a/tower-http/src/compression/layer.rs b/tower-http/src/compression/layer.rs index 0b45342c..b44f29c3 100644 --- a/tower-http/src/compression/layer.rs +++ b/tower-http/src/compression/layer.rs @@ -105,7 +105,7 @@ mod tests { } #[tokio::test] - async fn accept_encoding_configuration_works() -> Result<(), Box> { + async fn accept_encoding_configuration_works() -> Result<(), crate::BoxError> { let deflate_only_layer = CompressionLayer::new().no_br().no_gzip(); let mut service = ServiceBuilder::new() diff --git a/tower-http/src/compression/mod.rs b/tower-http/src/compression/mod.rs index ce5f0e79..dd3dce77 100644 --- a/tower-http/src/compression/mod.rs +++ b/tower-http/src/compression/mod.rs @@ -13,10 +13,10 @@ //! use tokio::fs::{self, File}; //! use tokio_util::io::ReaderStream; //! use tower::{Service, ServiceExt, ServiceBuilder, service_fn}; -//! use tower_http::compression::CompressionLayer; +//! use tower_http::{compression::CompressionLayer, BoxError}; //! //! # #[tokio::main] -//! # async fn main() -> Result<(), Box> { +//! # async fn main() -> Result<(), BoxError> { //! async fn handle(req: Request) -> Result, Infallible> { //! // Open the file. //! let file = File::open("Cargo.toml").await.expect("file missing"); diff --git a/tower-http/src/compression_utils.rs b/tower-http/src/compression_utils.rs index f96a1ace..e135600e 100644 --- a/tower-http/src/compression_utils.rs +++ b/tower-http/src/compression_utils.rs @@ -1,5 +1,6 @@ //! Types used by compression and decompression middleware. +use crate::{content_encoding::SupportedEncodings, BoxError}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::ready; @@ -14,8 +15,6 @@ use std::{ use tokio::io::AsyncRead; use tokio_util::io::{poll_read_buf, StreamReader}; -use crate::{content_encoding::SupportedEncodings, BodyOrIoError}; - #[derive(Debug, Clone, Copy)] pub(crate) struct AcceptEncoding { pub(crate) gzip: bool, @@ -155,10 +154,11 @@ impl WrapBody { impl Body for WrapBody where B: Body, + B::Error: Into, M: DecorateAsyncRead>, { type Data = Bytes; - type Error = BodyOrIoError; + type Error = BoxError; fn poll_data( self: Pin<&mut Self>, @@ -177,12 +177,12 @@ where .take(); if let Some(body_error) = body_error { - return Poll::Ready(Some(Err(BodyOrIoError::Body(body_error)))); + return Poll::Ready(Some(Err(body_error.into()))); } else if err.raw_os_error() == Some(SENTINEL_ERROR_CODE) { // SENTINEL_ERROR_CODE only gets used when storing an underlying body error unreachable!() } else { - return Poll::Ready(Some(Err(BodyOrIoError::Io(err)))); + return Poll::Ready(Some(Err(err.into()))); } } }; @@ -203,7 +203,7 @@ where .get_pin_mut() .get_pin_mut() .get_pin_mut(); - body.poll_trailers(cx).map_err(BodyOrIoError::Body) + body.poll_trailers(cx).map_err(Into::into) } } diff --git a/tower-http/src/decompression/body.rs b/tower-http/src/decompression/body.rs index f3bdcacc..d7d9f6cf 100644 --- a/tower-http/src/decompression/body.rs +++ b/tower-http/src/decompression/body.rs @@ -1,7 +1,9 @@ #![allow(unused_imports)] -use crate::compression_utils::{AsyncReadBody, BodyIntoStream, DecorateAsyncRead, WrapBody}; -use crate::BodyOrIoError; +use crate::{ + compression_utils::{AsyncReadBody, BodyIntoStream, DecorateAsyncRead, WrapBody}, + BoxError, +}; #[cfg(feature = "decompression-br")] use async_compression::tokio::bufread::BrotliDecoder; #[cfg(feature = "decompression-gzip")] @@ -132,9 +134,10 @@ where impl Body for DecompressionBody where B: Body, + B::Error: Into, { type Data = Bytes; - type Error = BodyOrIoError; + type Error = BoxError; fn poll_data( self: Pin<&mut Self>, @@ -152,7 +155,7 @@ where let bytes = buf.copy_to_bytes(buf.remaining()); Poll::Ready(Some(Ok(bytes))) } - Some(Err(err)) => Poll::Ready(Some(Err(BodyOrIoError::Body(err)))), + Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), None => Poll::Ready(None), }, } @@ -169,7 +172,7 @@ where BodyInnerProj::Deflate(inner) => inner.poll_trailers(cx), #[cfg(feature = "decompression-br")] BodyInnerProj::Brotli(inner) => inner.poll_trailers(cx), - BodyInnerProj::Identity(body) => body.poll_trailers(cx).map_err(BodyOrIoError::Body), + BodyInnerProj::Identity(body) => body.poll_trailers(cx).map_err(Into::into), } } } diff --git a/tower-http/src/decompression/mod.rs b/tower-http/src/decompression/mod.rs index 8fd9b6d4..020c3a45 100644 --- a/tower-http/src/decompression/mod.rs +++ b/tower-http/src/decompression/mod.rs @@ -9,10 +9,10 @@ //! use hyper::Body; //! use std::convert::Infallible; //! use tower::{Service, ServiceExt, ServiceBuilder, service_fn}; -//! use tower_http::{compression::Compression, decompression::DecompressionLayer}; +//! use tower_http::{compression::Compression, decompression::DecompressionLayer, BoxError}; //! # //! # #[tokio::main] -//! # async fn main() -> Result<(), Box> { +//! # async fn main() -> Result<(), tower_http::BoxError> { //! # async fn handle(req: Request) -> Result, Infallible> { //! # let body = Body::from("Hello, World!"); //! # Ok(Response::new(body)) @@ -45,7 +45,7 @@ //! let chunk = chunk?; //! bytes.extend_from_slice(&chunk[..]); //! } -//! let body = String::from_utf8(bytes.to_vec())?; +//! let body = String::from_utf8(bytes.to_vec()).map_err(Into::::into)?; //! //! assert_eq!(body, "Hello, World!"); //! # diff --git a/tower-http/src/lib.rs b/tower-http/src/lib.rs index 5d33ef09..a076e452 100644 --- a/tower-http/src/lib.rs +++ b/tower-http/src/lib.rs @@ -285,52 +285,6 @@ pub mod cors; pub mod classify; pub mod services; -/// Error type containing either a body error or an IO error. -/// -/// This type is used to combine errors produced by response bodies with compression or -/// decompression applied. The body itself can produce errors of type `E` whereas compression or -/// decompression can produce [`io::Error`]s. -/// -/// [`io::Error`]: std::io::Error -#[cfg(any(feature = "compression", feature = "decompression"))] -#[cfg_attr( - docsrs, - doc(cfg(any(feature = "compression", feature = "decompression"))) -)] -#[derive(Debug)] -pub enum BodyOrIoError { - /// Errors produced by the body. - Body(E), - /// IO errors produced by compression or decompression. - Io(std::io::Error), -} - -#[cfg(any(feature = "compression", feature = "decompression"))] -impl std::fmt::Display for BodyOrIoError -where - E: std::fmt::Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BodyOrIoError::Io(inner) => inner.fmt(f), - BodyOrIoError::Body(inner) => inner.fmt(f), - } - } -} - -#[cfg(any(feature = "compression", feature = "decompression"))] -impl std::error::Error for BodyOrIoError -where - E: std::error::Error, -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - BodyOrIoError::Io(inner) => inner.source(), - BodyOrIoError::Body(inner) => inner.source(), - } - } -} - /// The latency unit used to report latencies by middleware. #[non_exhaustive] #[derive(Copy, Clone, Debug)] @@ -342,3 +296,6 @@ pub enum LatencyUnit { /// Use nanoseconds. Nanos, } + +/// Alias for a type-erased error type. +pub type BoxError = Box;