use crate::body::SdkBody;
use bytes::Buf;
use bytes::Bytes;
use bytes_utils::SegmentedBuf;
use http_body::combinators::BoxBody;
use http_body::Body;
use pin_project::pin_project;
use std::error::Error as StdError;
use std::fmt::{Debug, Formatter};
use std::io::IoSlice;
use std::path::Path;
use std::pin::Pin;
use std::task::{Context, Poll};
#[cfg(feature = "bytestream-util")]
mod bytestream_util;
#[pin_project]
#[derive(Debug)]
pub struct ByteStream(#[pin] Inner<SdkBody>);
impl ByteStream {
pub fn new(body: SdkBody) -> Self {
Self(Inner::new(body))
}
pub fn from_static(bytes: &'static [u8]) -> Self {
Self(Inner::new(SdkBody::from(Bytes::from_static(bytes))))
}
pub fn into_inner(self) -> SdkBody {
self.0.body
}
pub async fn collect(self) -> Result<AggregatedBytes, Error> {
self.0.collect().await.map_err(|err| Error(err))
}
#[cfg(feature = "bytestream-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "bytestream-util")))]
pub async fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
let path = path.as_ref();
let path_buf = path.to_path_buf();
let sz = tokio::fs::metadata(path)
.await
.map_err(|err| Error(err.into()))?
.len();
let body_loader = move || {
SdkBody::from_dyn(BoxBody::new(bytestream_util::PathBody::from_path(
path_buf.as_path(),
sz,
)))
};
Ok(ByteStream::new(SdkBody::retryable(body_loader)))
}
#[cfg(feature = "bytestream-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "bytestream-util")))]
pub async fn from_file(file: tokio::fs::File) -> Result<Self, Error> {
let sz = file
.metadata()
.await
.map_err(|err| Error(err.into()))?
.len();
let body = SdkBody::from_dyn(BoxBody::new(bytestream_util::PathBody::from_file(file, sz)));
Ok(ByteStream::new(body))
}
}
impl Default for ByteStream {
fn default() -> Self {
Self(Inner {
body: SdkBody::from(""),
})
}
}
impl From<SdkBody> for ByteStream {
fn from(inp: SdkBody) -> Self {
ByteStream::new(inp)
}
}
impl From<Bytes> for ByteStream {
fn from(input: Bytes) -> Self {
ByteStream::new(SdkBody::from(input))
}
}
impl From<Vec<u8>> for ByteStream {
fn from(input: Vec<u8>) -> Self {
Self::from(Bytes::from(input))
}
}
#[derive(Debug)]
pub struct Error(Box<dyn StdError + Send + Sync + 'static>);
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.0.as_ref() as _)
}
}
impl futures_core::stream::Stream for ByteStream {
type Item = Result<Bytes, Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.project().0.poll_next(cx).map_err(|e| Error(e))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
#[derive(Debug, Clone)]
pub struct AggregatedBytes(SegmentedBuf<Bytes>);
impl AggregatedBytes {
pub fn into_bytes(mut self) -> Bytes {
self.0.copy_to_bytes(self.0.remaining())
}
}
impl Buf for AggregatedBytes {
fn remaining(&self) -> usize {
self.0.remaining()
}
fn chunk(&self) -> &[u8] {
self.0.chunk()
}
fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize {
self.0.chunks_vectored(dst)
}
fn advance(&mut self, cnt: usize) {
self.0.advance(cnt)
}
fn copy_to_bytes(&mut self, len: usize) -> Bytes {
self.0.copy_to_bytes(len)
}
}
#[pin_project]
#[derive(Debug, Clone, PartialEq, Eq)]
struct Inner<B> {
#[pin]
body: B,
}
impl<B> Inner<B> {
pub fn new(body: B) -> Self {
Self { body }
}
pub async fn collect(self) -> Result<AggregatedBytes, B::Error>
where
B: http_body::Body<Data = Bytes>,
{
let mut output = SegmentedBuf::new();
let body = self.body;
crate::pin_mut!(body);
while let Some(buf) = body.data().await {
output.push(buf?);
}
Ok(AggregatedBytes(output))
}
}
impl<B> futures_core::stream::Stream for Inner<B>
where
B: http_body::Body,
{
type Item = Result<Bytes, B::Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.project().body.poll_data(cx) {
Poll::Ready(Some(Ok(mut data))) => {
let len = data.chunk().len();
let bytes = data.copy_to_bytes(len);
Poll::Ready(Some(Ok(bytes)))
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Pending => Poll::Pending,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let size_hint = http_body::Body::size_hint(&self.body);
(
size_hint.lower() as usize,
size_hint.upper().map(|u| u as usize),
)
}
}
#[cfg(test)]
mod tests {
use crate::byte_stream::{ByteStream, Inner};
use bytes::{Buf, Bytes};
use http_body::Body;
use std::error::Error;
#[tokio::test]
async fn read_from_string_body() {
let body = hyper::Body::from("a simple body");
assert_eq!(
Inner::new(body)
.collect()
.await
.expect("no errors")
.into_bytes(),
Bytes::from("a simple body")
);
}
#[tokio::test]
async fn read_from_channel_body() {
let (mut sender, body) = hyper::Body::channel();
let byte_stream = Inner::new(body);
tokio::spawn(async move {
sender.send_data(Bytes::from("data 1")).await.unwrap();
sender.send_data(Bytes::from("data 2")).await.unwrap();
sender.send_data(Bytes::from("data 3")).await.unwrap();
});
assert_eq!(
byte_stream.collect().await.expect("no errors").into_bytes(),
Bytes::from("data 1data 2data 3")
);
}
#[cfg(feature = "bytestream-util")]
#[tokio::test]
async fn path_based_bytestreams() -> Result<(), Box<dyn Error>> {
use std::io::Write;
use tempfile::NamedTempFile;
let mut file = NamedTempFile::new()?;
for i in 0..10000 {
writeln!(file, "Brian was here. Briefly. {}", i)?;
}
let body = ByteStream::from_path(&file).await?.into_inner();
assert_eq!(body.size_hint().exact(), Some(298890));
let mut body1 = body.try_clone().expect("retryable bodies are cloneable");
let some_data = body1
.data()
.await
.expect("should have some data")
.expect("read should not fail");
assert!(!some_data.is_empty());
let body2 = body.try_clone().expect("retryable bodies are cloneable");
let body3 = body.try_clone().expect("retryable bodies are cloneable");
let body2 = ByteStream::new(body2).collect().await?.into_bytes();
let body3 = ByteStream::new(body3).collect().await?.into_bytes();
assert_eq!(body2, body3);
assert!(body2.starts_with(b"Brian was here."));
assert!(body2.ends_with(b"9999\n"));
assert_eq!(body2.len(), 298890);
assert_eq!(
ByteStream::new(body1).collect().await?.remaining(),
298890 - some_data.len()
);
Ok(())
}
}