use std::error::Error; use std::fmt::Debug; use std::pin::Pin; use std::task::{Context, Poll}; use bytes::Buf; use http_body::{Body, Frame, SizeHint}; use proj::EitherProj; /// Sum type with two cases: [`Left`] and [`Right`], used if a body can be one of /// two distinct types. /// /// [`Left`]: Either::Left /// [`Right`]: Either::Right #[derive(Debug, Clone, Copy)] pub enum Either { /// A value of type `L` Left(L), /// A value of type `R` Right(R), } impl Either { /// This function is part of the generated code from `pin-project-lite`, /// for a more in depth explanation and the rest of the generated code refer /// to the [`proj`] module. pub(crate) fn project(self: Pin<&mut Self>) -> EitherProj { unsafe { match self.get_unchecked_mut() { Self::Left(left) => EitherProj::Left(Pin::new_unchecked(left)), Self::Right(right) => EitherProj::Right(Pin::new_unchecked(right)), } } } } impl Either { /// Convert [`Either`] into the inner type, if both `Left` and `Right` are /// of the same type. pub fn into_inner(self) -> L { match self { Either::Left(left) => left, Either::Right(right) => right, } } } impl Body for Either where L: Body, R: Body, L::Error: Into>, R::Error: Into>, Data: Buf, { type Data = Data; type Error = Box; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { match self.project() { EitherProj::Left(left) => left .poll_frame(cx) .map(|poll| poll.map(|opt| opt.map_err(Into::into))), EitherProj::Right(right) => right .poll_frame(cx) .map(|poll| poll.map(|opt| opt.map_err(Into::into))), } } fn is_end_stream(&self) -> bool { match self { Either::Left(left) => left.is_end_stream(), Either::Right(right) => right.is_end_stream(), } } fn size_hint(&self) -> SizeHint { match self { Either::Left(left) => left.size_hint(), Either::Right(right) => right.size_hint(), } } } pub(crate) mod proj { //! This code is the (cleaned output) generated by [pin-project-lite], as it //! does not support tuple variants. //! //! This is the altered expansion from the following snippet, expanded by //! `cargo-expand`: //! //! ```rust //! use pin_project_lite::pin_project; //! //! pin_project! { //! #[project = EitherProj] //! pub enum Either { //! Left {#[pin] left: L}, //! Right {#[pin] right: R} //! } //! } //! ``` //! //! [pin-project-lite]: https://docs.rs/pin-project-lite/latest/pin_project_lite/ use std::marker::PhantomData; use std::pin::Pin; use super::Either; #[allow(dead_code)] #[allow(single_use_lifetimes)] #[allow(unknown_lints)] #[allow(clippy::mut_mut)] #[allow(clippy::redundant_pub_crate)] #[allow(clippy::ref_option_ref)] #[allow(clippy::type_repetition_in_bounds)] pub(crate) enum EitherProj<'__pin, L, R> where Either: '__pin, { Left(Pin<&'__pin mut L>), Right(Pin<&'__pin mut R>), } #[allow(single_use_lifetimes)] #[allow(unknown_lints)] #[allow(clippy::used_underscore_binding)] #[allow(missing_debug_implementations)] const _: () = { #[allow(non_snake_case)] pub struct __Origin<'__pin, L, R> { __dummy_lifetime: PhantomData<&'__pin ()>, _Left: L, _Right: R, } impl<'__pin, L, R> Unpin for Either where __Origin<'__pin, L, R>: Unpin {} trait MustNotImplDrop {} #[allow(drop_bounds)] impl MustNotImplDrop for T {} impl MustNotImplDrop for Either {} }; } #[cfg(test)] mod tests { use super::*; use crate::{BodyExt, Empty, Full}; #[tokio::test] async fn data_left() { let full = Full::new(&b"hello"[..]); let mut value: Either<_, Empty<&[u8]>> = Either::Left(full); assert_eq!(value.size_hint().exact(), Some(b"hello".len() as u64)); assert_eq!( value.frame().await.unwrap().unwrap().into_data().unwrap(), &b"hello"[..] ); assert!(value.frame().await.is_none()); } #[tokio::test] async fn data_right() { let full = Full::new(&b"hello!"[..]); let mut value: Either, _> = Either::Right(full); assert_eq!(value.size_hint().exact(), Some(b"hello!".len() as u64)); assert_eq!( value.frame().await.unwrap().unwrap().into_data().unwrap(), &b"hello!"[..] ); assert!(value.frame().await.is_none()); } #[test] fn into_inner() { let a = Either::::Left(2); assert_eq!(a.into_inner(), 2) } }