use std::{convert::Infallible, sync::Arc};
use tokio::{io::DuplexStream, sync::mpsc::Sender};
use tonic::{
body::BoxBody,
codegen::{
http::{Request, Response},
Service,
},
server::NamedService,
transport::{Channel, Error as TransportError},
};
pub trait Svc:
Service<Request<BoxBody>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static
where
Self::Future: Send + 'static,
{
}
impl<S> Svc for S
where
S: Service<Request<BoxBody>, Response = Response<BoxBody>, Error = Infallible>
+ NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
{
}
pub type ConnectionMockSender = Sender<Result<DuplexStream, TransportError>>;
#[derive(Debug, Clone)]
pub struct EndpointMock {
connection_sender: Arc<ConnectionMockSender>,
}
impl EndpointMock {
pub fn new(connection_sender: ConnectionMockSender) -> Self {
Self {
connection_sender: Arc::new(connection_sender),
}
}
pub async fn once(self) -> Channel {
self.connect().await
}
pub async fn connect(&self) -> Channel {
let connection_sender = Arc::clone(&self.connection_sender);
let client_connector =
::tower::service_fn(move | uri: ::tonic::transport::Uri| {
tracing::info!("connection to {:?}", uri);
let connection_sender = Arc::clone(&connection_sender);
async move {
let (client_io, server_io) = ::tokio::io::duplex(1024);
connection_sender.send(Ok(server_io)).await.unwrap();
Ok::<_, ::tonic::transport::Error>(::hyper_util::rt::TokioIo::new(client_io))
}
});
::tonic::transport::Endpoint::try_from("http://[::1]:50051/pseudo-endpoint")
.unwrap()
.connect_with_connector(client_connector)
.await
.unwrap()
}
}
#[macro_export]
macro_rules! mock_server_fn {
($vis:vis $fn_name:ident; $($svc:ident),+; $logger:path) => {
$vis async fn $fn_name(
$($svc: impl $crate::Svc<Future: Send>),+
) -> (impl ::futures::Future<Output = ()>, $crate::EndpointMock) {
use $logger::{info};
let (connection_sender, connections_receiver) = ::tokio::sync::mpsc::channel(32);
let incoming_connections: ::tokio_stream::wrappers::ReceiverStream<
Result<::tokio::io::DuplexStream, ::tonic::transport::Error>,
> = ::tokio_stream::wrappers::ReceiverStream::from(connections_receiver);
let router = ::tonic::transport::Server::builder()$(
.add_service($svc))+;
let server_future = async move {
info!("start grpc server");
router
.serve_with_incoming(incoming_connections)
.await
.unwrap();
info!("grpc server stopped");
};
let connector = $crate::EndpointMock::new(connection_sender);
(server_future, connector)
}
};
($fn_name:ident; $($svc:ident),+; $logger:path) => {
mock_server_fn!(pub(crate) $fn_name; $($svc),+; $logger);
};
($vis:vis $fn_name:ident; $($svc:ident),+) => {
mock_server_fn!($vis $fn_name; $($svc),+; ::tracing);
};
($fn_name:ident; $($svc:ident),+) => {
mock_server_fn!(pub(crate) $fn_name; $($svc),+; ::tracing);
};
}