Documentation
use hyper::body::{Body, Buf, Frame};
use std::error::Error;
use std::pin::Pin;
use std::task::{Context, Poll};

/// 擦除掉具体的body和error
trait ErasedBody: Send + Sync {
    fn poll_frame(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<Result<Frame<Box<dyn Buf + Send>>, Box<dyn Error + Send + Sync>>>>;
}

/// 为所有满足条件的 Body 实现 ErasedBody
impl<B> ErasedBody for B
where
    B: Body + Send + Sync + 'static,
    B::Data: Buf + Send + 'static,
    B::Error: Error + Send + Sync + 'static,
{
    fn poll_frame(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<Result<Frame<Box<dyn Buf + Send>>, Box<dyn Error + Send + Sync>>>> {
        match <Self as Body>::poll_frame(self, cx) {
            Poll::Pending => Poll::Pending,
            Poll::Ready(None) => Poll::Ready(None),
            Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(Box::new(e)))),
            Poll::Ready(Some(Ok(frame))) => {
                let mapped = frame.map_data(|d| Box::new(d) as Box<dyn Buf + Send>);
                Poll::Ready(Some(Ok(mapped)))
            }
        }
    }
}

/// 通用型http body
pub struct VariantBody {
    inner: Pin<Box<dyn ErasedBody + 'static>>,
}

impl VariantBody {
    pub fn new<B>(body: B) -> Self
    where
        B: Body + Send + Sync + 'static,
        B::Data: Buf + Send + 'static,
        B::Error: Error + Send + Sync + 'static,
    {
        Self {
            inner: Box::pin(body),
        }
    }
}

/// 为VariantBody实现body trait
impl Body for VariantBody {
    type Data = Box<dyn Buf + Send>;
    type Error = Box<dyn Error + Send + Sync>;

    fn poll_frame(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
        self.get_mut().inner.as_mut().poll_frame(cx)
    }
}

pub type Empty = http_body_util::Empty<hyper::body::Bytes>;

pub type Full = http_body_util::Full<hyper::body::Bytes>;

pub fn empty() -> VariantBody {
    VariantBody::new(Empty::new())
}

pub fn variant_body<B>(body: B) -> VariantBody
where
    B: Body + Send + Sync + 'static,
    B::Data: Buf + Send + 'static,
    B::Error: Error + Send + Sync + 'static,
{
    VariantBody::new(body)
}