use std::future::Future;
#[cfg(feature = "tls")]
use thiserror::Error;
use tower::Service;
use self::oneshot::Oneshot;
#[cfg(feature = "tls")]
pub use self::tls::{StaticHostTlsTransport, TlsRequest, TlsTransport};
use crate::info::HasConnectionInfo;
#[cfg(feature = "duplex")]
pub mod duplex;
#[cfg(feature = "mock")]
pub mod mock;
pub mod tcp;
#[cfg(feature = "tls")]
pub mod tls;
#[cfg(target_family = "unix")]
pub mod unix;
pub trait Transport<Req> {
type IO: HasConnectionInfo + Send + 'static;
type Error: std::error::Error + Send + Sync + 'static;
type Future: Future<Output = Result<Self::IO, <Self as Transport<Req>>::Error>> + Send + 'static;
fn connect(&mut self, req: &Req) -> <Self as Transport<Req>>::Future;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), <Self as Transport<Req>>::Error>>;
}
impl<T, IO, Req, F, E> Transport<Req> for T
where
T: for<'a> Service<&'a Req, Response = IO, Future = F, Error = E>,
T: Clone + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
F: Future<Output = Result<IO, E>> + Send + 'static,
IO: HasConnectionInfo + Send + 'static,
Req: Send,
{
type IO = IO;
type Error = E;
type Future = F;
fn connect(&mut self, req: &Req) -> Self::Future {
self.call(req)
}
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), <Self as Transport<Req>>::Error>> {
Service::poll_ready(self, cx)
}
}
pub trait TransportExt<Req>: Transport<Req> {
fn oneshot(self, request: Req) -> Oneshot<Self, Req>
where
Self: Sized,
{
Oneshot::new(self, request)
}
}
impl<T, Req> TransportExt<Req> for T where T: Transport<Req> {}
#[cfg(feature = "tls")]
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TlsConnectionError<E> {
#[error(transparent)]
Connection(#[from] E),
#[error("TLS handshake failed: {0}")]
Handshake(#[source] std::io::Error),
#[error("No domain found in URI")]
NoDomain,
#[error("TLS is not enabled, can't connect to https")]
TlsDisabled,
}
mod oneshot {
use std::{fmt, future::Future, task::ready};
use super::Transport;
#[pin_project::pin_project(project=OneshotStateProj)]
enum OneshotState<T, R>
where
T: Transport<R>,
{
Pending { transport: T, request: Option<R> },
Ready(#[pin] T::Future),
}
impl<T, R> fmt::Debug for OneshotState<T, R>
where
T: Transport<R>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OneshotState::Pending { .. } => f.debug_struct("OneshotState::Pending").finish(),
OneshotState::Ready(_) => f.debug_struct("OneshotState::Ready").finish(),
}
}
}
#[derive(Debug)]
#[pin_project::pin_project]
pub struct Oneshot<T, R>
where
T: Transport<R>,
{
#[pin]
state: OneshotState<T, R>,
}
impl<T, R> Oneshot<T, R>
where
T: Transport<R>,
{
pub fn new(transport: T, request: R) -> Self {
Self {
state: OneshotState::Pending {
transport,
request: Some(request),
},
}
}
}
impl<T, R> Future for Oneshot<T, R>
where
T: Transport<R>,
{
type Output = Result<T::IO, T::Error>;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let mut this = self.project();
loop {
match this.state.as_mut().project() {
OneshotStateProj::Pending { transport, request } => {
ready!(transport.poll_ready(cx))?;
let fut = transport.connect(request.as_ref().unwrap());
this.state.set(OneshotState::Ready(fut));
}
OneshotStateProj::Ready(fut) => {
return fut.poll(cx);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::future::Ready;
use static_assertions::assert_obj_safe;
assert_obj_safe!(Transport<(), IO=(), Error=(), Future=Ready<Result<(),()>>>);
}