use crate::observability::{ReqwestStreamErrorHandler, ReqwestStreamProgress, ReqwestStreamProgressHandler};
use crate::{StreamBodyError, StreamBodyResult};
use bytes::Bytes;
use futures::stream::BoxStream;
use futures::{Stream, StreamExt};
use http_streams_core::format::{StreamFormat, StreamFormatEncode};
use http_streams_core::{
buffer_bytes, buffer_ready_items, count_bytes, count_items, encode_stream, instrument,
Counting, Direction, Progress, ProgressOptions, Side, StreamContext, StreamErrorKind,
};
use reqwest::header::HeaderValue;
use std::sync::Arc;
use std::time::Duration;
#[non_exhaustive]
pub struct ReqwestStreamBodyOptions {
pub content_type: Option<HeaderValue>,
pub buffering_bytes: Option<usize>,
pub buffering_ready_items: Option<usize>,
pub on_error: Option<ReqwestStreamErrorHandler>,
pub on_progress: Option<ReqwestStreamProgressHandler>,
pub progress_interval: Option<Duration>,
pub progress_items: Option<u64>,
}
impl Default for ReqwestStreamBodyOptions {
fn default() -> Self {
Self::new()
}
}
impl ReqwestStreamBodyOptions {
pub fn new() -> Self {
Self {
content_type: None,
buffering_bytes: None,
buffering_ready_items: None,
on_error: None,
on_progress: None,
progress_interval: Some(http_streams_core::DEFAULT_PROGRESS_INTERVAL),
progress_items: None,
}
}
pub fn content_type(mut self, content_type: HeaderValue) -> Self {
self.content_type = Some(content_type);
self
}
pub fn buffering_bytes(mut self, size: usize) -> Self {
self.buffering_bytes = Some(size);
self
}
pub fn buffering_ready_items(mut self, count: usize) -> Self {
self.buffering_ready_items = Some(count);
self
}
pub fn on_error<F>(mut self, handler: F) -> Self
where
F: Fn(&StreamBodyError) + Send + Sync + 'static,
{
self.on_error = Some(Arc::new(handler));
self
}
pub fn on_progress<F>(mut self, handler: F) -> Self
where
F: Fn(&ReqwestStreamProgress) + Send + Sync + 'static,
{
self.on_progress = Some(Arc::new(handler));
self
}
pub fn progress_interval(mut self, interval: Duration) -> Self {
self.progress_interval = Some(interval);
self
}
pub fn progress_items(mut self, items: u64) -> Self {
self.progress_items = Some(items);
self
}
fn progress_options(&self) -> ProgressOptions {
let mut opts = ProgressOptions::new();
opts.on_error = self.on_error.clone();
opts.on_progress = self.on_progress.clone();
opts.progress_interval = self.progress_interval;
opts.progress_items = self.progress_items;
opts
}
}
pub struct ReqwestStreamBody {
stream: BoxStream<'static, StreamBodyResult<Bytes>>,
content_type: HeaderValue,
}
impl std::fmt::Debug for ReqwestStreamBody {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReqwestStreamBody")
.field("content_type", &self.content_type)
.finish_non_exhaustive()
}
}
impl ReqwestStreamBody {
pub fn new<S, T, FMT>(format: FMT, stream: S) -> Self
where
FMT: StreamFormatEncode<T> + StreamFormat,
FMT::Encoder: Send + 'static,
S: Stream<Item = T> + Send + 'static,
T: Send + 'static,
{
Self::with_options(format, stream, ReqwestStreamBodyOptions::new())
}
pub fn try_new<S, T, FMT, E>(format: FMT, stream: S) -> Self
where
FMT: StreamFormatEncode<T> + StreamFormat,
FMT::Encoder: Send + 'static,
S: Stream<Item = Result<T, E>> + Send + 'static,
T: Send + 'static,
E: Into<Box<dyn std::error::Error + Send + Sync>> + Send + 'static,
{
Self::try_with_options(format, stream, ReqwestStreamBodyOptions::new())
}
pub fn with_options<S, T, FMT>(
format: FMT,
stream: S,
options: ReqwestStreamBodyOptions,
) -> Self
where
FMT: StreamFormatEncode<T> + StreamFormat,
FMT::Encoder: Send + 'static,
S: Stream<Item = T> + Send + 'static,
T: Send + 'static,
{
Self::build(format, stream.map(Ok), options)
}
pub fn try_with_options<S, T, FMT, E>(
format: FMT,
stream: S,
options: ReqwestStreamBodyOptions,
) -> Self
where
FMT: StreamFormatEncode<T> + StreamFormat,
FMT::Encoder: Send + 'static,
S: Stream<Item = Result<T, E>> + Send + 'static,
T: Send + 'static,
E: Into<Box<dyn std::error::Error + Send + Sync>> + Send + 'static,
{
let normalised = stream.map(|item| {
item.map_err(|err| {
StreamBodyError::new(StreamErrorKind::InputOutputError, Some(err.into()), None)
})
});
Self::build(format, normalised, options)
}
fn build<S, T, FMT>(format: FMT, stream: S, options: ReqwestStreamBodyOptions) -> Self
where
FMT: StreamFormatEncode<T> + StreamFormat,
FMT::Encoder: Send + 'static,
S: Stream<Item = StreamBodyResult<T>> + Send + 'static,
T: Send + 'static,
{
let content_type = options.content_type.clone().unwrap_or_else(|| {
HeaderValue::from_static(format.default_content_type())
});
let context = StreamContext::new(format.format_name(), Direction::Request, Side::Client)
.content_type(content_type.to_str().unwrap_or_default());
let context = match options.buffering_bytes {
Some(bytes) => context.buf_capacity(bytes),
None => context,
};
let progress = Progress::new(&context, &options.progress_options());
let items = Box::pin(count_items(stream, &progress));
let bytes = encode_stream(items, format.encoder());
let buffered: BoxStream<'static, StreamBodyResult<Bytes>> =
match (options.buffering_ready_items, options.buffering_bytes) {
(Some(count), _) => Box::pin(buffer_ready_items(bytes, count)),
(_, Some(size)) => Box::pin(buffer_bytes(bytes, size)),
(None, None) => Box::pin(bytes),
};
let counted = count_bytes(buffered, &progress);
let stream = Box::pin(instrument(Box::pin(counted), progress, Counting::Bytes));
Self {
stream,
content_type,
}
}
pub fn content_type(&self) -> &HeaderValue {
&self.content_type
}
pub fn into_stream(self) -> BoxStream<'static, StreamBodyResult<Bytes>> {
self.stream
}
}
impl From<ReqwestStreamBody> for reqwest::Body {
fn from(body: ReqwestStreamBody) -> Self {
reqwest::Body::wrap_stream(body.stream)
}
}
pub trait StreamBodyRequest {
fn stream_body(self, body: ReqwestStreamBody) -> reqwest::RequestBuilder;
}
impl StreamBodyRequest for reqwest::RequestBuilder {
fn stream_body(self, body: ReqwestStreamBody) -> reqwest::RequestBuilder {
let mut headers = reqwest::header::HeaderMap::with_capacity(1);
headers.insert(reqwest::header::CONTENT_TYPE, body.content_type().clone());
self.headers(headers).body(reqwest::Body::from(body))
}
}