use crate::api::client::media::multipart::{MultipartBody, MultipartState};
use crate::api::client::media::{MediaUpload, MediaUploadStream};
use crate::errors::ApiCallError;
use crate::ConnectRequirements;
use bytes::{Buf, Bytes};
use derive_more::Debug;
use futures_core::Stream;
use futures_util::AsyncRead;
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Full, StreamBody};
use hyper::body::{Body, Frame};
use media::{AsyncMediaUpload, AsyncMediaUploadStream};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::io::{Read, Seek};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use url::Url;
#[derive(Default, Debug)]
pub enum OutgoingBodyContent<const N: usize = { 1024 * 10 }> {
#[default]
Empty,
Json(String),
Stream(MediaUpload<dyn MediaUploadStream>),
AsyncStream(AsyncMediaUpload<dyn AsyncMediaUploadStream>),
Multipart(MultipartBody),
}
impl OutgoingBodyContent {
pub fn from_body_and_media(
body: Option<impl Into<String>>,
media: Option<MediaUpload<dyn MediaUploadStream>>,
) -> Self {
match (body, media) {
(Some(body), Some(media)) => {
OutgoingBodyContent::Multipart(MultipartBody::new(media, body))
}
(None, Some(media)) => OutgoingBodyContent::Stream(media),
(Some(body), None) => OutgoingBodyContent::Json(body.into()),
(None, None) => OutgoingBodyContent::Empty,
}
}
pub fn get_content_type(&self) -> Option<String> {
Some(match self {
OutgoingBodyContent::Empty => return None,
OutgoingBodyContent::Json(_) => "application/json".into(),
OutgoingBodyContent::Stream(x) => x.mime_type.as_str().into(),
OutgoingBodyContent::AsyncStream(x) => x.mime_type.as_str().into(),
OutgoingBodyContent::Multipart(multipart) => {
format!("multipart/related; boundary={}", multipart.boundary)
}
})
}
pub fn get_length(&self) -> u64 {
match self {
OutgoingBodyContent::Empty => 0,
OutgoingBodyContent::Json(string) => Bytes::from(string.to_string()).len() as u64,
OutgoingBodyContent::Stream(x) => x.length,
OutgoingBodyContent::AsyncStream(x) => x.length,
OutgoingBodyContent::Multipart(x) => {
x.get_body_before_media().len() as u64
+ x.media_body.length
+ x.get_body_after_media().len() as u64
}
}
}
fn poll_stream(
cx: &mut Context,
stream: &mut MediaUpload<dyn MediaUploadStream>,
) -> Poll<Option<Result<Bytes, Box<dyn Error + Send + Sync + 'static>>>> {
stream.body.as_mut().poll_next(cx)
}
}
impl<const ASYNC_STREAM_BUF_SIZE: usize> OutgoingBodyContent<ASYNC_STREAM_BUF_SIZE> {
fn poll_stream_async(
cx: &mut Context,
stream: &mut AsyncMediaUpload<dyn AsyncMediaUploadStream>,
) -> Poll<Option<Result<Bytes, Box<dyn Error + Send + Sync + 'static>>>> {
let mut buf = [0u8; ASYNC_STREAM_BUF_SIZE];
let x = Pin::new(&mut stream.body).poll_read(cx, &mut buf)?;
match x {
Poll::Ready(x) => {
let x = Bytes::copy_from_slice(&buf[0..x]);
Poll::Ready(Some(Ok(x)))
}
Poll::Pending => Poll::Pending,
}
}
}
type PollResult<T, E> = Poll<Option<Result<T, Box<E>>>>;
fn bytes_to_frame<E: ?Sized>(data: PollResult<Bytes, E>) -> PollResult<Frame<Bytes>, E> {
match data {
Poll::Ready(Some(Ok(data))) => {
if data.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(Frame::data(data))))
}
}
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
pub mod media;
impl Body for OutgoingBodyContent {
type Data = Bytes;
type Error = Box<dyn Error + Send + Sync + 'static>;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
match &*self {
OutgoingBodyContent::Empty => Poll::Ready(None),
OutgoingBodyContent::Json(value) => {
let json_str = value.to_string();
*self.get_mut() = OutgoingBodyContent::Empty;
Poll::Ready(Some(Ok(Frame::data(Bytes::from(json_str)))))
}
OutgoingBodyContent::Multipart(_) => {
use media::multipart::MultipartState;
let multipart = self.get_mut();
let multipart = match multipart {
OutgoingBodyContent::Multipart(multipart) => multipart,
_ => unreachable!(),
};
match multipart.state {
MultipartState::NotStarted => {
multipart.state = MultipartState::Polling;
let body_string = multipart.get_body_before_media();
Poll::Ready(Some(Ok(Frame::data(Bytes::from(body_string)))))
}
MultipartState::Polling => {
let poll = Self::poll_stream(cx, &mut multipart.media_body);
match poll {
Poll::Ready(None) => {
multipart.state = MultipartState::Done;
let string = multipart.get_body_after_media();
Poll::Ready(Some(Ok(Frame::data(Bytes::from(string)))))
}
Poll::Ready(Some(Ok(x))) => Poll::Ready(Some(Ok(Frame::data(x)))),
_ => bytes_to_frame(poll),
}
}
MultipartState::Done => Poll::Ready(None),
}
}
OutgoingBodyContent::Stream(_) => {
let stream = self.get_mut();
let stream = match stream {
OutgoingBodyContent::Stream(stream) => stream,
_ => unreachable!(),
};
bytes_to_frame(Self::poll_stream(cx, stream))
}
OutgoingBodyContent::AsyncStream(_) => {
let stream = self.get_mut();
let stream = match stream {
OutgoingBodyContent::AsyncStream(stream) => stream,
_ => unreachable!(),
};
bytes_to_frame(Self::poll_stream_async(cx, stream))
}
}
}
fn is_end_stream(&self) -> bool {
match self {
OutgoingBodyContent::Empty => true,
OutgoingBodyContent::Json(_) => false, OutgoingBodyContent::Stream(_) => false,
OutgoingBodyContent::AsyncStream(_) => false,
OutgoingBodyContent::Multipart(_) => false,
}
}
fn size_hint(&self) -> http_body::SizeHint {
http_body::SizeHint::default()
}
}