From 00fcbf9385ea40f966656f039784ef405838dd5b Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Wed, 8 Feb 2023 12:10:57 +0100 Subject: [PATCH 1/3] Make `Full`'s and `Empty`'s error type generic It is not uncommon to have a function that creatures a boxed body via `Full` or `Empty`: ```rust fn box_body_from_bytes(bytes: Bytes) -> UnsyncBoxBody { Full::new(bytes) .map_err(|e| match e {}) .boxed_unsync() } ``` The `.map_err(|e| match e {})` dance is necessary because `Full` always uses `Infallible` as its error type, so we have to convert that into whatever error type we actually need. This will often be hyper's, tonic's, or axum's error type. However if we make `Full` generic over the error type: ```rust pub struct Full { ... } ``` then Rust can just infer it and we avoid having to call `map_err`: ```rust fn box_body_from_bytes(bytes: Bytes) -> UnsyncBoxBody { Full::new(bytes).boxed_unsync() } ``` I think this is quite a bit nicer. It is especially nice that we avoid having to type `match e {}` which is quite confusing unless you've see it before. The downside to this is that if the error type cannot be infered you have to explicitly set it when creating a `Full`: ```rust Full::<_, Error>::new(bytes) ``` That makes this a breaking change. --- http-body-util/src/collected.rs | 3 +- http-body-util/src/combinators/box_body.rs | 8 +-- http-body-util/src/either.rs | 12 ++-- http-body-util/src/empty.rs | 19 +++---- http-body-util/src/full.rs | 66 ++++++++++++++++------ http-body-util/src/limited.rs | 4 +- http-body/src/lib.rs | 1 + 7 files changed, 73 insertions(+), 40 deletions(-) diff --git a/http-body-util/src/collected.rs b/http-body-util/src/collected.rs index 34e3bf7..bbdfdeb 100644 --- a/http-body-util/src/collected.rs +++ b/http-body-util/src/collected.rs @@ -98,7 +98,7 @@ mod tests { #[tokio::test] async fn full_body() { - let body = Full::new(&b"hello"[..]); + let body = Full::<_, Infallible>::new(&b"hello"[..]); let buffered = body.collect().await.unwrap(); @@ -121,6 +121,7 @@ mod tests { } #[tokio::test] + #[allow(unused_must_use)] async fn delayed_segments() { let one = stream::once(async { Ok::<_, Infallible>(Frame::data(&b"hello "[..])) }); let two = stream::once(async { diff --git a/http-body-util/src/combinators/box_body.rs b/http-body-util/src/combinators/box_body.rs index 2ec8010..b3e28d8 100644 --- a/http-body-util/src/combinators/box_body.rs +++ b/http-body-util/src/combinators/box_body.rs @@ -1,5 +1,3 @@ -use crate::BodyExt as _; - use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use std::{ @@ -63,9 +61,10 @@ where impl Default for BoxBody where D: Buf + 'static, + E: 'static, { fn default() -> Self { - BoxBody::new(crate::Empty::new().map_err(|err| match err {})) + BoxBody::new(crate::Empty::new()) } } @@ -115,8 +114,9 @@ where impl Default for UnsyncBoxBody where D: Buf + 'static, + E: 'static, { fn default() -> Self { - UnsyncBoxBody::new(crate::Empty::new().map_err(|err| match err {})) + UnsyncBoxBody::new(crate::Empty::new()) } } diff --git a/http-body-util/src/either.rs b/http-body-util/src/either.rs index 740f207..fdea95f 100644 --- a/http-body-util/src/either.rs +++ b/http-body-util/src/either.rs @@ -144,14 +144,16 @@ pub(crate) mod proj { #[cfg(test)] mod tests { + use std::convert::Infallible; + use super::*; - use crate::{BodyExt, Empty, Full}; + use crate::{Empty, Full, BodyExt}; #[tokio::test] async fn data_left() { - let full = Full::new(&b"hello"[..]); + let full = Full::<_, Infallible>::new(&b"hello"[..]); - let mut value: Either<_, Empty<&[u8]>> = Either::Left(full); + let mut value: Either<_, Empty<&[u8], Infallible>> = Either::Left(full); assert_eq!(value.size_hint().exact(), Some(b"hello".len() as u64)); assert_eq!( @@ -163,9 +165,9 @@ mod tests { #[tokio::test] async fn data_right() { - let full = Full::new(&b"hello!"[..]); + let full = Full::<_, Infallible>::new(&b"hello!"[..]); - let mut value: Either, _> = Either::Right(full); + let mut value: Either, _> = Either::Right(full); assert_eq!(value.size_hint().exact(), Some(b"hello!".len() as u64)); assert_eq!( diff --git a/http-body-util/src/empty.rs b/http-body-util/src/empty.rs index 0c6adea..c523ae3 100644 --- a/http-body-util/src/empty.rs +++ b/http-body-util/src/empty.rs @@ -1,7 +1,6 @@ use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use std::{ - convert::Infallible, fmt, marker::PhantomData, pin::Pin, @@ -9,20 +8,20 @@ use std::{ }; /// A body that is always empty. -pub struct Empty { - _marker: PhantomData D>, +pub struct Empty { + _marker: PhantomData (D, E)>, } -impl Empty { +impl Empty { /// Create a new `Empty`. pub fn new() -> Self { Self::default() } } -impl Body for Empty { +impl Body for Empty { type Data = D; - type Error = Infallible; + type Error = E; #[inline] fn poll_frame( @@ -41,13 +40,13 @@ impl Body for Empty { } } -impl fmt::Debug for Empty { +impl fmt::Debug for Empty { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Empty").finish() } } -impl Default for Empty { +impl Default for Empty { fn default() -> Self { Self { _marker: PhantomData, @@ -55,7 +54,7 @@ impl Default for Empty { } } -impl Clone for Empty { +impl Clone for Empty { fn clone(&self) -> Self { Self { _marker: PhantomData, @@ -63,4 +62,4 @@ impl Clone for Empty { } } -impl Copy for Empty {} +impl Copy for Empty {} diff --git a/http-body-util/src/full.rs b/http-body-util/src/full.rs index 30f0c6a..bbbeb44 100644 --- a/http-body-util/src/full.rs +++ b/http-body-util/src/full.rs @@ -2,19 +2,21 @@ use bytes::{Buf, Bytes}; use http_body::{Body, Frame, SizeHint}; use pin_project_lite::pin_project; use std::borrow::Cow; -use std::convert::{Infallible, TryFrom}; +use std::convert::TryFrom; +use std::fmt; +use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; pin_project! { /// A body that consists of a single chunk. - #[derive(Clone, Copy, Debug)] - pub struct Full { + pub struct Full { data: Option, + _marker: PhantomData E>, } } -impl Full +impl Full where D: Buf, { @@ -25,16 +27,19 @@ where } else { None }; - Full { data } + Full { + data, + _marker: PhantomData, + } } } -impl Body for Full +impl Body for Full where D: Buf, { type Data = D; - type Error = Infallible; + type Error = E; fn poll_frame( mut self: Pin<&mut Self>, @@ -55,17 +60,40 @@ where } } -impl Default for Full +impl Clone for Full +where + D: Clone, +{ + fn clone(&self) -> Self { + Self { + data: self.data.clone(), + _marker: self._marker, + } + } +} + +impl Copy for Full where D: Copy {} + +impl fmt::Debug for Full +where + D: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Full").field("data", &self.data).finish() + } +} + +impl Default for Full where D: Buf, { /// Create an empty `Full`. fn default() -> Self { - Full { data: None } + Full { data: None, _marker: PhantomData } } } -impl From for Full +impl From for Full where D: Buf + From, { @@ -74,7 +102,7 @@ where } } -impl From> for Full +impl From> for Full where D: Buf + From>, { @@ -83,7 +111,7 @@ where } } -impl From<&'static [u8]> for Full +impl From<&'static [u8]> for Full where D: Buf + From<&'static [u8]>, { @@ -92,7 +120,7 @@ where } } -impl From> for Full +impl From> for Full where D: Buf + From<&'static B> + From, B: ToOwned + ?Sized, @@ -105,7 +133,7 @@ where } } -impl From for Full +impl From for Full where D: Buf + From, { @@ -114,7 +142,7 @@ where } } -impl From<&'static str> for Full +impl From<&'static str> for Full where D: Buf + From<&'static str>, { @@ -125,12 +153,14 @@ where #[cfg(test)] mod tests { + use std::convert::Infallible; + use super::*; use crate::BodyExt; #[tokio::test] async fn full_returns_some() { - let mut full = Full::new(&b"hello"[..]); + let mut full = Full::<_, Infallible>::new(&b"hello"[..]); assert_eq!(full.size_hint().exact(), Some(b"hello".len() as u64)); assert_eq!( full.frame().await.unwrap().unwrap().into_data().unwrap(), @@ -141,7 +171,7 @@ mod tests { #[tokio::test] async fn empty_full_returns_none() { - assert!(Full::<&[u8]>::default().frame().await.is_none()); - assert!(Full::new(&b""[..]).frame().await.is_none()); + assert!(Full::<&[u8], Infallible>::default().frame().await.is_none()); + assert!(Full::<_, Infallible>::new(&b""[..]).frame().await.is_none()); } } diff --git a/http-body-util/src/limited.rs b/http-body-util/src/limited.rs index 239c46c..f57aee8 100644 --- a/http-body-util/src/limited.rs +++ b/http-body-util/src/limited.rs @@ -110,7 +110,7 @@ mod tests { #[tokio::test] async fn read_for_body_under_limit_returns_data() { const DATA: &[u8] = b"testing"; - let inner = Full::new(Bytes::from(DATA)); + let inner = Full::<_, Infallible>::new(Bytes::from(DATA)); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); @@ -128,7 +128,7 @@ mod tests { #[tokio::test] async fn read_for_body_over_limit_returns_error() { const DATA: &[u8] = b"testing a string that is too long"; - let inner = Full::new(Bytes::from(DATA)); + let inner = Full::<_, Infallible>::new(Bytes::from(DATA)); let body = &mut Limited::new(inner, 8); let mut hint = SizeHint::new(); diff --git a/http-body/src/lib.rs b/http-body/src/lib.rs index 877d1c2..45660d5 100644 --- a/http-body/src/lib.rs +++ b/http-body/src/lib.rs @@ -42,6 +42,7 @@ pub trait Body { type Error; /// Attempt to pull out the next data buffer of this stream. + #[allow(clippy::type_complexity)] fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, From 668844f73057de83b05b1b6d53b88ad0d9a5d54f Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Wed, 8 Feb 2023 12:25:34 +0100 Subject: [PATCH 2/3] format --- http-body-util/src/either.rs | 2 +- http-body-util/src/full.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/http-body-util/src/either.rs b/http-body-util/src/either.rs index fdea95f..8a67178 100644 --- a/http-body-util/src/either.rs +++ b/http-body-util/src/either.rs @@ -147,7 +147,7 @@ mod tests { use std::convert::Infallible; use super::*; - use crate::{Empty, Full, BodyExt}; + use crate::{BodyExt, Empty, Full}; #[tokio::test] async fn data_left() { diff --git a/http-body-util/src/full.rs b/http-body-util/src/full.rs index bbbeb44..990409c 100644 --- a/http-body-util/src/full.rs +++ b/http-body-util/src/full.rs @@ -89,7 +89,10 @@ where { /// Create an empty `Full`. fn default() -> Self { - Full { data: None, _marker: PhantomData } + Full { + data: None, + _marker: PhantomData, + } } } From dfbefc9fd5ed44a33e260ef2456c977b5b197393 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Wed, 8 Feb 2023 13:09:28 +0100 Subject: [PATCH 3/3] Default error type to `Infallible` --- http-body-util/src/empty.rs | 3 ++- http-body-util/src/full.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/http-body-util/src/empty.rs b/http-body-util/src/empty.rs index c523ae3..dc315e0 100644 --- a/http-body-util/src/empty.rs +++ b/http-body-util/src/empty.rs @@ -1,6 +1,7 @@ use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use std::{ + convert::Infallible, fmt, marker::PhantomData, pin::Pin, @@ -8,7 +9,7 @@ use std::{ }; /// A body that is always empty. -pub struct Empty { +pub struct Empty { _marker: PhantomData (D, E)>, } diff --git a/http-body-util/src/full.rs b/http-body-util/src/full.rs index 990409c..19f3a64 100644 --- a/http-body-util/src/full.rs +++ b/http-body-util/src/full.rs @@ -2,7 +2,7 @@ use bytes::{Buf, Bytes}; use http_body::{Body, Frame, SizeHint}; use pin_project_lite::pin_project; use std::borrow::Cow; -use std::convert::TryFrom; +use std::convert::{Infallible, TryFrom}; use std::fmt; use std::marker::PhantomData; use std::pin::Pin; @@ -10,7 +10,7 @@ use std::task::{Context, Poll}; pin_project! { /// A body that consists of a single chunk. - pub struct Full { + pub struct Full { data: Option, _marker: PhantomData E>, }