http_body_util/
either.rs

1use std::error::Error;
2use std::fmt::Debug;
3use std::pin::Pin;
4use std::task::{Context, Poll};
5
6use bytes::Buf;
7use http_body::{Body, Frame, SizeHint};
8use proj::EitherProj;
9
10/// Sum type with two cases: [`Left`] and [`Right`], used if a body can be one of
11/// two distinct types.
12///
13/// [`Left`]: Either::Left
14/// [`Right`]: Either::Right
15#[derive(Debug, Clone, Copy)]
16pub enum Either<L, R> {
17    /// A value of type `L`
18    Left(L),
19    /// A value of type `R`
20    Right(R),
21}
22
23impl<L, R> Either<L, R> {
24    /// This function is part of the generated code from `pin-project-lite`,
25    /// for a more in depth explanation and the rest of the generated code refer
26    /// to the [`proj`] module.
27    pub(crate) fn project(self: Pin<&mut Self>) -> EitherProj<L, R> {
28        unsafe {
29            match self.get_unchecked_mut() {
30                Self::Left(left) => EitherProj::Left(Pin::new_unchecked(left)),
31                Self::Right(right) => EitherProj::Right(Pin::new_unchecked(right)),
32            }
33        }
34    }
35}
36
37impl<L> Either<L, L> {
38    /// Convert [`Either`] into the inner type, if both `Left` and `Right` are
39    /// of the same type.
40    pub fn into_inner(self) -> L {
41        match self {
42            Either::Left(left) => left,
43            Either::Right(right) => right,
44        }
45    }
46}
47
48impl<L, R, Data> Body for Either<L, R>
49where
50    L: Body<Data = Data>,
51    R: Body<Data = Data>,
52    L::Error: Into<Box<dyn Error + Send + Sync>>,
53    R::Error: Into<Box<dyn Error + Send + Sync>>,
54    Data: Buf,
55{
56    type Data = Data;
57    type Error = Box<dyn Error + Send + Sync>;
58
59    fn poll_frame(
60        self: Pin<&mut Self>,
61        cx: &mut Context<'_>,
62    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
63        match self.project() {
64            EitherProj::Left(left) => left
65                .poll_frame(cx)
66                .map(|poll| poll.map(|opt| opt.map_err(Into::into))),
67            EitherProj::Right(right) => right
68                .poll_frame(cx)
69                .map(|poll| poll.map(|opt| opt.map_err(Into::into))),
70        }
71    }
72
73    fn is_end_stream(&self) -> bool {
74        match self {
75            Either::Left(left) => left.is_end_stream(),
76            Either::Right(right) => right.is_end_stream(),
77        }
78    }
79
80    fn size_hint(&self) -> SizeHint {
81        match self {
82            Either::Left(left) => left.size_hint(),
83            Either::Right(right) => right.size_hint(),
84        }
85    }
86}
87
88pub(crate) mod proj {
89    //! This code is the (cleaned output) generated by [pin-project-lite], as it
90    //! does not support tuple variants.
91    //!
92    //! This is the altered expansion from the following snippet, expanded by
93    //! `cargo-expand`:
94    //!
95    //! ```rust
96    //! use pin_project_lite::pin_project;
97    //!
98    //! pin_project! {
99    //!     #[project = EitherProj]
100    //!     pub enum Either<L, R> {
101    //!         Left {#[pin] left: L},
102    //!         Right {#[pin] right: R}
103    //!     }
104    //! }
105    //! ```
106    //!
107    //! [pin-project-lite]: https://docs.rs/pin-project-lite/latest/pin_project_lite/
108    use std::marker::PhantomData;
109    use std::pin::Pin;
110
111    use super::Either;
112
113    #[allow(dead_code)]
114    #[allow(single_use_lifetimes)]
115    #[allow(unknown_lints)]
116    #[allow(clippy::mut_mut)]
117    #[allow(clippy::redundant_pub_crate)]
118    #[allow(clippy::ref_option_ref)]
119    #[allow(clippy::type_repetition_in_bounds)]
120    pub(crate) enum EitherProj<'__pin, L, R>
121    where
122        Either<L, R>: '__pin,
123    {
124        Left(Pin<&'__pin mut L>),
125        Right(Pin<&'__pin mut R>),
126    }
127
128    #[allow(single_use_lifetimes)]
129    #[allow(unknown_lints)]
130    #[allow(clippy::used_underscore_binding)]
131    #[allow(missing_debug_implementations)]
132    const _: () = {
133        #[allow(non_snake_case)]
134        pub struct __Origin<'__pin, L, R> {
135            __dummy_lifetime: PhantomData<&'__pin ()>,
136            _Left: L,
137            _Right: R,
138        }
139        impl<'__pin, L, R> Unpin for Either<L, R> where __Origin<'__pin, L, R>: Unpin {}
140
141        trait MustNotImplDrop {}
142        #[allow(drop_bounds)]
143        impl<T: Drop> MustNotImplDrop for T {}
144        impl<L, R> MustNotImplDrop for Either<L, R> {}
145    };
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::{BodyExt, Empty, Full};
152
153    #[tokio::test]
154    async fn data_left() {
155        let full = Full::new(&b"hello"[..]);
156
157        let mut value: Either<_, Empty<&[u8]>> = Either::Left(full);
158
159        assert_eq!(value.size_hint().exact(), Some(b"hello".len() as u64));
160        assert_eq!(
161            value.frame().await.unwrap().unwrap().into_data().unwrap(),
162            &b"hello"[..]
163        );
164        assert!(value.frame().await.is_none());
165    }
166
167    #[tokio::test]
168    async fn data_right() {
169        let full = Full::new(&b"hello!"[..]);
170
171        let mut value: Either<Empty<&[u8]>, _> = Either::Right(full);
172
173        assert_eq!(value.size_hint().exact(), Some(b"hello!".len() as u64));
174        assert_eq!(
175            value.frame().await.unwrap().unwrap().into_data().unwrap(),
176            &b"hello!"[..]
177        );
178        assert!(value.frame().await.is_none());
179    }
180
181    #[test]
182    fn into_inner() {
183        let a = Either::<i32, i32>::Left(2);
184        assert_eq!(a.into_inner(), 2)
185    }
186}