#![cfg_attr(irpc_docsrs, feature(doc_cfg))]
use std::{fmt::Debug, future::Future, io, marker::PhantomData, ops::Deref};
#[cfg(feature = "derive")]
pub use irpc_derive::rpc_requests;
use n0_error::stack_error;
use serde::{Serialize, de::DeserializeOwned};
#[cfg(feature = "rpc")]
use tracing::Instrument;
use self::{
channel::{
SendError, mpsc,
none::{NoReceiver, NoSender},
oneshot,
},
sealed::Sealed,
};
pub mod channel;
#[cfg(feature = "rpc")]
pub mod span_propagation;
#[cfg(test)]
mod tests;
pub mod util;
#[cfg(not(feature = "rpc"))]
pub mod rpc {
pub struct RemoteSender<S>(std::marker::PhantomData<S>);
}
#[cfg(feature = "rpc")]
pub mod rpc;
#[cfg(feature = "iroh")]
pub mod iroh {
pub use crate::rpc::iroh_impl::*;
}
#[cfg(feature = "rpc")]
pub mod noq {
pub use crate::rpc::noq_impl::*;
}
mod sealed {
pub trait Sealed {}
}
#[doc(hidden)]
pub mod __macro_exports {
#[cfg(feature = "rpc")]
pub use ::noq::{RecvStream, SendStream};
pub use serde;
pub use tracing;
}
pub trait RpcMessage: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static {}
impl<T> RpcMessage for T where
T: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static
{
}
pub trait Service: Serialize + DeserializeOwned + Send + Sync + Debug + 'static {
type Message: Send + Unpin + 'static;
const SPAN_PROPAGATION: bool = false;
fn new_request_span(&self, req_id: u64) -> tracing::Span {
tracing::debug_span!("request", req = req_id)
}
}
pub trait Sender: Debug + Sealed {}
pub trait Receiver: Debug + Sealed {}
pub trait Channels<S: Service>: Send + 'static {
type Tx: Sender;
type Rx: Receiver;
}
#[non_exhaustive]
pub struct WithChannels<I: Channels<S>, S: Service> {
pub inner: I,
pub tx: <I as Channels<S>>::Tx,
pub rx: <I as Channels<S>>::Rx,
pub span: tracing::Span,
}
impl<I: Channels<S> + Debug, S: Service> Debug for WithChannels<I, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("")
.field(&self.inner)
.field(&self.tx)
.field(&self.rx)
.finish()
}
}
impl<I: Channels<S>, S: Service, Tx, Rx> From<(I, Tx, Rx)> for WithChannels<I, S>
where
I: Channels<S>,
<I as Channels<S>>::Tx: From<Tx>,
<I as Channels<S>>::Rx: From<Rx>,
{
fn from(inner: (I, Tx, Rx)) -> Self {
let (inner, tx, rx) = inner;
Self {
inner,
tx: tx.into(),
rx: rx.into(),
span: tracing::Span::current(),
}
}
}
impl<I, S, Tx> From<(I, Tx)> for WithChannels<I, S>
where
I: Channels<S, Rx = NoReceiver>,
S: Service,
<I as Channels<S>>::Tx: From<Tx>,
{
fn from(inner: (I, Tx)) -> Self {
let (inner, tx) = inner;
Self {
inner,
tx: tx.into(),
rx: NoReceiver,
span: tracing::Span::current(),
}
}
}
impl<I, S> From<(I,)> for WithChannels<I, S>
where
I: Channels<S, Rx = NoReceiver, Tx = NoSender>,
S: Service,
{
fn from(inner: (I,)) -> Self {
let (inner,) = inner;
Self {
inner,
tx: NoSender,
rx: NoReceiver,
span: tracing::Span::current(),
}
}
}
impl<I: Channels<S>, S: Service> Deref for WithChannels<I, S> {
type Target = I;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug)]
pub struct Client<S: Service>(ClientInner<S::Message>, PhantomData<S>);
impl<S: Service> Clone for Client<S> {
fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData)
}
}
impl<S: Service> From<LocalSender<S>> for Client<S> {
fn from(tx: LocalSender<S>) -> Self {
Self(ClientInner::Local(tx.0), PhantomData)
}
}
impl<S: Service> From<tokio::sync::mpsc::Sender<S::Message>> for Client<S> {
fn from(tx: tokio::sync::mpsc::Sender<S::Message>) -> Self {
LocalSender::from(tx).into()
}
}
impl<S: Service> Client<S> {
#[cfg(feature = "rpc")]
pub fn noq(endpoint: ::noq::Endpoint, addr: std::net::SocketAddr) -> Self {
Self::boxed(rpc::noq_impl::NoqLazyRemoteConnection::new(endpoint, addr))
}
#[cfg(feature = "iroh")]
pub fn iroh(
endpoint: ::iroh::Endpoint,
addr: impl Into<::iroh::EndpointAddr>,
alpn: impl AsRef<[u8]>,
) -> Self {
let conn = rpc::iroh_impl::IrohLazyRemoteConnection::new(
endpoint,
addr.into(),
alpn.as_ref().to_vec(),
);
Self::boxed(conn)
}
#[cfg(feature = "rpc")]
pub fn boxed(remote: impl rpc::RemoteConnection) -> Self {
Self(ClientInner::Remote(Box::new(remote)), PhantomData)
}
pub fn local(tx: impl Into<crate::channel::mpsc::Sender<S::Message>>) -> Self {
let tx: crate::channel::mpsc::Sender<S::Message> = tx.into();
Self(ClientInner::Local(tx), PhantomData)
}
pub fn as_local(&self) -> Option<LocalSender<S>> {
match &self.0 {
ClientInner::Local(tx) => Some(tx.clone().into()),
ClientInner::Remote(..) => None,
}
}
#[allow(clippy::type_complexity)]
pub fn request(
&self,
) -> impl Future<Output = Result<Request<LocalSender<S>, rpc::RemoteSender<S>>, RequestError>> + use<S>
{
#[cfg(feature = "rpc")]
{
let cloned = match &self.0 {
ClientInner::Local(tx) => Request::Local(tx.clone()),
ClientInner::Remote(connection) => Request::Remote(connection.clone_boxed()),
};
async move {
match cloned {
Request::Local(tx) => Ok(Request::Local(tx.into())),
Request::Remote(conn) => {
let (send, recv) = conn.open_bi().await?;
Ok(Request::Remote(rpc::RemoteSender::new(send, recv)))
}
}
}
}
#[cfg(not(feature = "rpc"))]
{
let ClientInner::Local(tx) = &self.0 else {
unreachable!()
};
let tx = tx.clone().into();
async move { Ok(Request::Local(tx)) }
}
}
pub fn client_streaming<Req, Update, Res>(
&self,
msg: Req,
local_update_cap: usize,
) -> impl Future<Output = Result<(mpsc::Sender<Update>, oneshot::Receiver<Res>)>>
+ use<Req, Update, Res, S>
where
S: From<Req>,
S::Message: From<WithChannels<Req, S>>,
Req: Channels<S, Tx = oneshot::Sender<Res>, Rx = mpsc::Receiver<Update>>,
Update: RpcMessage,
Res: RpcMessage,
{
let request = self.request();
async move {
let (update_tx, res_rx): (mpsc::Sender<Update>, oneshot::Receiver<Res>) =
match request.await? {
Request::Local(request) => {
let (req_tx, req_rx) = mpsc::channel(local_update_cap);
let (res_tx, res_rx) = oneshot::channel();
request.send((msg, res_tx, req_rx)).await?;
(req_tx, res_rx)
}
#[cfg(not(feature = "rpc"))]
Request::Remote(_request) => unreachable!(),
#[cfg(feature = "rpc")]
Request::Remote(request) => {
let (tx, rx, span) = request.write_in_span(msg).await?;
span.in_scope(|| (tx.into(), rx.into()))
}
};
Ok((update_tx, res_rx))
}
}
pub fn notify_streaming<Req, Update>(
&self,
msg: Req,
local_update_cap: usize,
) -> impl Future<Output = Result<mpsc::Sender<Update>>>
where
S: From<Req>,
S::Message: From<WithChannels<Req, S>>,
Req: Channels<S, Tx = NoSender, Rx = mpsc::Receiver<Update>>,
Update: RpcMessage,
{
let client = self.clone();
async move {
let request = client.request().await?;
match request {
Request::Local(request) => {
let (req_tx, req_rx) = mpsc::channel(local_update_cap);
request.send((msg, NoSender, req_rx)).await?;
Ok(req_tx)
}
#[cfg(not(feature = "rpc"))]
Request::Remote(_request) => unreachable!(),
#[cfg(feature = "rpc")]
Request::Remote(remote) => {
let (send, _, span) = remote.write_in_span(msg).await?;
Ok(span.in_scope(|| send.into()))
}
}
}
}
pub fn bidi_streaming<Req, Update, Res>(
&self,
msg: Req,
local_update_cap: usize,
local_response_cap: usize,
) -> impl Future<Output = Result<(mpsc::Sender<Update>, mpsc::Receiver<Res>)>>
+ Send
+ 'static
+ use<Req, Update, Res, S>
where
S: From<Req>,
S::Message: From<WithChannels<Req, S>>,
Req: Channels<S, Tx = mpsc::Sender<Res>, Rx = mpsc::Receiver<Update>>,
Update: RpcMessage,
Res: RpcMessage,
{
let request = self.request();
async move {
let (update_tx, res_rx): (mpsc::Sender<Update>, mpsc::Receiver<Res>) =
match request.await? {
Request::Local(request) => {
let (update_tx, update_rx) = mpsc::channel(local_update_cap);
let (res_tx, res_rx) = mpsc::channel(local_response_cap);
request.send((msg, res_tx, update_rx)).await?;
(update_tx, res_rx)
}
#[cfg(not(feature = "rpc"))]
Request::Remote(_request) => unreachable!(),
#[cfg(feature = "rpc")]
Request::Remote(request) => {
let (tx, rx, span) = request.write_in_span(msg).await?;
span.in_scope(|| (tx.into(), rx.into()))
}
};
Ok((update_tx, res_rx))
}
}
pub fn notify<Req>(&self, msg: Req) -> impl Future<Output = Result<()>> + Send + 'static
where
S: From<Req>,
S::Message: From<WithChannels<Req, S>>,
Req: Channels<S, Tx = NoSender, Rx = NoReceiver>,
{
let this = self.clone();
async move {
match this.request().await? {
Request::Local(request) => {
request.send((msg,)).await?;
}
#[cfg(not(feature = "rpc"))]
Request::Remote(_request) => unreachable!(),
#[cfg(feature = "rpc")]
Request::Remote(request) => {
this.write_remote(request, msg).await?;
}
};
Ok(())
}
}
pub fn rpc<Req, Res>(&self, msg: Req) -> impl Future<Output = Result<Res>> + Send + 'static
where
S: From<Req>,
S::Message: From<WithChannels<Req, S>>,
Req: Channels<S, Tx = oneshot::Sender<Res>, Rx = NoReceiver>,
Res: RpcMessage,
{
let this = self.clone();
async move {
match this.request().await? {
Request::Local(request) => {
let (tx, rx) = oneshot::channel();
request.send((msg, tx)).await?;
Ok(rx.await?)
}
#[cfg(not(feature = "rpc"))]
Request::Remote(_request) => unreachable!(),
#[cfg(feature = "rpc")]
Request::Remote(request) => {
let (rx, span) = this.write_remote(request, msg).await?;
let rx: oneshot::Receiver<Res> = span.in_scope(|| rx.into());
Ok(rx.instrument(span).await?)
}
}
}
}
pub fn server_streaming<Req, Res>(
&self,
msg: Req,
local_response_cap: usize,
) -> impl Future<Output = Result<mpsc::Receiver<Res>>> + Send + 'static + use<Req, Res, S>
where
S: From<Req>,
S::Message: From<WithChannels<Req, S>>,
Req: Channels<S, Tx = mpsc::Sender<Res>, Rx = NoReceiver>,
Res: RpcMessage,
{
let this = self.clone();
async move {
let recv: mpsc::Receiver<Res> = match this.request().await? {
Request::Local(request) => {
let (tx, rx) = mpsc::channel(local_response_cap);
request.send((msg, tx)).await?;
rx
}
#[cfg(not(feature = "rpc"))]
Request::Remote(_request) => unreachable!(),
#[cfg(feature = "rpc")]
Request::Remote(request) => {
let (rx, span) = this.write_remote(request, msg).await?;
span.in_scope(|| rx.into())
}
};
Ok(recv)
}
}
#[cfg(feature = "rpc")]
async fn write_remote(
&self,
mut request: rpc::RemoteSender<S>,
msg: impl Into<S>,
) -> Result<(::noq::RecvStream, tracing::Span)> {
let (span, buf) = request.prepare(msg)?;
match request.write_raw(&buf).instrument(span.clone()).await {
Ok((_tx, rx)) => {
if !self.0.zero_rtt_rejected().await {
return Ok((rx, span));
}
}
Err(err) if err.is_zero_rtt_rejected() => {}
Err(err) => return Err(err.into()),
}
let Request::Remote(request) = self.request().await? else {
unreachable!()
};
span.record("req", request.req_id());
let (_tx, rx) = request.write_raw(&buf).instrument(span.clone()).await?;
Ok((rx, span))
}
}
#[derive(Debug)]
pub(crate) enum ClientInner<M> {
Local(crate::channel::mpsc::Sender<M>),
#[cfg(feature = "rpc")]
Remote(Box<dyn rpc::RemoteConnection>),
#[cfg(not(feature = "rpc"))]
#[allow(dead_code)]
Remote(PhantomData<M>),
}
impl<M> Clone for ClientInner<M> {
fn clone(&self) -> Self {
match self {
Self::Local(tx) => Self::Local(tx.clone()),
#[cfg(feature = "rpc")]
Self::Remote(conn) => Self::Remote(conn.clone_boxed()),
#[cfg(not(feature = "rpc"))]
Self::Remote(_) => unreachable!(),
}
}
}
impl<M> ClientInner<M> {
#[allow(dead_code)]
async fn zero_rtt_rejected(&self) -> bool {
match self {
ClientInner::Local(_sender) => false,
#[cfg(feature = "rpc")]
ClientInner::Remote(remote_connection) => remote_connection.zero_rtt_rejected().await,
#[cfg(not(feature = "rpc"))]
Self::Remote(_) => unreachable!(),
}
}
}
#[stack_error(derive, add_meta)]
#[non_exhaustive]
pub enum RequestError {
#[cfg(feature = "rpc")]
#[error("Error establishing connection")]
Connect {
#[error(std_err)]
source: ::noq::ConnectError,
},
#[cfg(feature = "iroh")]
#[error("Error establishing connection")]
IrohConnect {
source: ::iroh::endpoint::ConnectError,
},
#[cfg(feature = "rpc")]
#[error("Connection {close}")]
ConnectionClosed { close: rpc::ConnectionClose },
#[cfg(feature = "rpc")]
#[error("Connection failed")]
ConnectionFailed {
#[error(std_err)]
source: ::noq::ConnectionError,
},
}
#[stack_error(derive, add_meta, from_sources)]
#[non_exhaustive]
pub enum Error {
#[error("Request error")]
Request { source: RequestError },
#[error("Send error")]
Send { source: channel::SendError },
#[error("Mpsc recv error")]
MpscRecv { source: channel::mpsc::RecvError },
#[error("Oneshot recv error")]
OneshotRecv { source: channel::oneshot::RecvError },
}
#[cfg(feature = "rpc")]
impl Error {
pub fn remote_code(&self) -> Option<rpc::ErrorCode> {
match self {
Self::Request { source, .. } => source.remote_code(),
Self::Send { source, .. } => source.remote_code(),
Self::MpscRecv { source, .. } => source.remote_code(),
Self::OneshotRecv { source, .. } => source.remote_code(),
}
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
impl From<Error> for io::Error {
fn from(e: Error) -> Self {
match e {
Error::Request { source, .. } => source.into(),
Error::Send { source, .. } => source.into(),
Error::MpscRecv { source, .. } => source.into(),
Error::OneshotRecv { source, .. } => source.into(),
}
}
}
#[cfg(feature = "rpc")]
impl RequestError {
pub fn remote_code(&self) -> Option<rpc::ErrorCode> {
match self {
Self::ConnectionClosed { close, .. } => close.code(),
_ => None,
}
}
}
impl From<RequestError> for io::Error {
fn from(e: RequestError) -> Self {
match e {
#[cfg(feature = "rpc")]
RequestError::Connect { source, .. } => io::Error::other(source),
#[cfg(feature = "rpc")]
RequestError::ConnectionClosed { ref close, .. } => {
let kind = close.io_kind();
io::Error::new(kind, e)
}
#[cfg(feature = "rpc")]
RequestError::ConnectionFailed { source, .. } => source.into(),
#[cfg(feature = "iroh")]
RequestError::IrohConnect { source, .. } => io::Error::other(source),
}
}
}
#[cfg(feature = "rpc")]
impl From<::noq::ConnectError> for RequestError {
fn from(source: ::noq::ConnectError) -> Self {
n0_error::e!(RequestError::Connect, source)
}
}
#[cfg(feature = "iroh")]
impl From<::iroh::endpoint::ConnectError> for RequestError {
fn from(source: ::iroh::endpoint::ConnectError) -> Self {
n0_error::e!(RequestError::IrohConnect, source)
}
}
#[cfg(feature = "rpc")]
impl From<::noq::ConnectionError> for RequestError {
fn from(source: ::noq::ConnectionError) -> Self {
match rpc::ConnectionClose::from_error(&source) {
Some(close) => n0_error::e!(RequestError::ConnectionClosed { close }),
None => n0_error::e!(RequestError::ConnectionFailed, source),
}
}
}
#[derive(Debug)]
pub struct LocalSender<S: Service>(crate::channel::mpsc::Sender<S::Message>);
impl<S: Service> Clone for LocalSender<S> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<S: Service> From<tokio::sync::mpsc::Sender<S::Message>> for LocalSender<S> {
fn from(tx: tokio::sync::mpsc::Sender<S::Message>) -> Self {
Self(tx.into())
}
}
impl<S: Service> From<crate::channel::mpsc::Sender<S::Message>> for LocalSender<S> {
fn from(tx: crate::channel::mpsc::Sender<S::Message>) -> Self {
Self(tx)
}
}
#[derive(Debug)]
pub enum Request<L, R> {
Local(L),
Remote(R),
}
impl<S: Service> LocalSender<S> {
pub fn send<T>(
&self,
value: impl Into<WithChannels<T, S>>,
) -> impl Future<Output = Result<(), SendError>> + Send + 'static
where
T: Channels<S>,
S::Message: From<WithChannels<T, S>>,
{
let value: S::Message = value.into().into();
self.send_raw(value)
}
pub fn send_raw(
&self,
value: S::Message,
) -> impl Future<Output = Result<(), SendError>> + Send + 'static + use<S> {
let x = self.0.clone();
async move { x.send(value).await }
}
}