use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use bytes::Bytes;
use futures::{Stream, StreamExt};
use foundation_core::valtron::{MappedSender, PipeReceiver, PipeSender};
use foundation_errstacks::ErrorTrace;
use foundation_netio::shared::http::{RequestDescriptor, SimpleHeaders, Status};
use crate::shared::error::{Code, ConnectError};
use super::capabilities::TransportCapabilities;
pub type ByteSink = PipeSender<Bytes>;
pub trait SendBody: Send + Sync + 'static {
fn try_send(&self, item: Bytes) -> Result<(), ()>;
fn send_async(&self, item: Bytes) -> SendBodyFuture<'_>;
fn close(&self);
fn is_closed(&self) -> bool;
}
pub type SendBodyFuture<'a> = Pin<Box<dyn Future<Output = Result<(), ()>> + Send + 'a>>;
impl SendBody for PipeSender<Bytes> {
fn try_send(&self, item: Bytes) -> Result<(), ()> {
self.try_send(item).map_err(|_| ())
}
fn send_async(&self, item: Bytes) -> SendBodyFuture<'_> {
Box::pin(async move { self.send(item).await.map_err(|_| ()) })
}
fn close(&self) { self.close(); }
fn is_closed(&self) -> bool { self.is_closed() }
}
impl<Inner: Send + 'static, F> SendBody for MappedSender<Inner, Bytes, F>
where
F: Fn(&Bytes) -> Inner + Send + Sync + 'static,
{
fn try_send(&self, item: Bytes) -> Result<(), ()> {
self.try_send(&item)
}
fn send_async(&self, item: Bytes) -> SendBodyFuture<'_> {
Box::pin(async move { self.send(item).await.map_err(|_| ()) })
}
fn close(&self) { self.close(); }
fn is_closed(&self) -> bool { self.is_closed() }
}
pub type ByteSource = PipeReceiver<Bytes>;
pub type HeadSource = PipeReceiver<(Status, SimpleHeaders)>;
pub type HeadStream =
Pin<Box<dyn Stream<Item = Result<(Status, SimpleHeaders), TransportError>> + Send>>;
pub type BodyStream =
Pin<Box<dyn Stream<Item = Result<Bytes, TransportError>> + Send>>;
#[must_use]
pub fn body_stream_from_pipe(rx: ByteSource) -> BodyStream {
Box::pin(futures::stream::unfold(rx, |rx| async move {
match rx.receive().await {
Some(bytes) => Some((Ok(bytes), rx)),
None => None,
}
}))
}
#[must_use]
pub fn head_stream_from_pipe(rx: HeadSource) -> HeadStream {
Box::pin(futures::stream::unfold(rx, |rx| async move {
match rx.receive().await {
Some(head) => Some((Ok(head), rx)),
None => None,
}
}))
}
pub trait Transport: Send + Sync + 'static {
fn capabilities(&self) -> TransportCapabilities;
fn open(&self, request: RequestDescriptor) -> Result<TransportStream, TransportError>;
}
pub struct TransportStream {
pub send_body: Arc<dyn SendBody>,
pub head: HeadStream,
pub recv_body: BodyStream,
pub trailers: PipeReceiver<SimpleHeaders>,
}
#[derive(Debug, Clone)]
pub enum TransportError {
Connect(Arc<dyn std::error::Error + Send + Sync + 'static>),
Timeout,
Reset,
Protocol(String),
Canceled,
Io(Arc<std::io::Error>),
}
impl core::fmt::Display for TransportError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TransportError::Connect(e) => write!(f, "transport connect failure: {e}"),
TransportError::Timeout => write!(f, "transport timeout"),
TransportError::Reset => write!(f, "transport reset mid-exchange"),
TransportError::Protocol(m) => write!(f, "transport protocol error: {m}"),
TransportError::Canceled => write!(f, "transport canceled"),
TransportError::Io(e) => write!(f, "transport i/o error: {e}"),
}
}
}
impl std::error::Error for TransportError {}
impl TransportError {
#[must_use]
pub fn code(&self) -> Code {
match self {
TransportError::Connect(_) | TransportError::Reset | TransportError::Io(_) => {
Code::Unavailable
}
TransportError::Timeout => Code::DeadlineExceeded,
TransportError::Protocol(_) => Code::Internal,
TransportError::Canceled => Code::Canceled,
}
}
}
impl From<TransportError> for ConnectError {
fn from(err: TransportError) -> Self {
ConnectError::new(err.code(), err.to_string())
}
}
impl From<TransportError> for ErrorTrace<ConnectError> {
fn from(err: TransportError) -> Self {
let connect = ConnectError::new(err.code(), err.to_string());
ErrorTrace::new(err).change_context(connect)
}
}
pub async fn round_trip(
transport: &impl Transport,
request: RequestDescriptor,
body: Bytes,
) -> Result<(Status, SimpleHeaders, Bytes), TransportError> {
let mut stream = transport.open(request)?;
if !body.is_empty() {
stream
.send_body
.try_send(body)
.map_err(|e| {
TransportError::Io(Arc::new(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
format!("{e:?}"),
)))
})?;
}
stream.send_body.close();
let (status, headers) = stream
.head
.next()
.await
.ok_or(TransportError::Reset)??;
let mut body_bytes = Vec::new();
while let Some(chunk) = stream.recv_body.next().await {
body_bytes.extend_from_slice(&chunk?);
}
Ok((status, headers, Bytes::from(body_bytes)))
}