use alloy_json_rpc::{RpcParam, RpcReturn};
use alloy_rpc_client::{RpcCall, Waiter};
use alloy_transport::{Transport, TransportResult};
use futures::FutureExt;
use pin_project::pin_project;
use serde_json::value::RawValue;
use std::{
future::Future,
pin::Pin,
task::{self, Poll},
};
use tokio::sync::oneshot;
#[pin_project(project = ProviderCallProj)]
pub enum ProviderCall<Conn, Params, Resp, Output = Resp, Map = fn(Resp) -> Output>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
Map: Fn(Resp) -> Output,
{
RpcCall(RpcCall<Conn, Params, Resp, Output, Map>),
Waiter(Waiter<Resp, Output, Map>),
BoxedFuture(Pin<Box<dyn Future<Output = TransportResult<Output>> + Send>>),
Ready(Option<TransportResult<Output>>),
}
impl<Conn, Params, Resp, Output, Map> ProviderCall<Conn, Params, Resp, Output, Map>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
Map: Fn(Resp) -> Output,
{
pub const fn ready(output: TransportResult<Output>) -> Self {
Self::Ready(Some(output))
}
pub const fn is_rpc_call(&self) -> bool {
matches!(self, Self::RpcCall(_))
}
pub const fn as_rpc_call(&self) -> Option<&RpcCall<Conn, Params, Resp, Output, Map>> {
match self {
Self::RpcCall(call) => Some(call),
_ => None,
}
}
pub fn as_mut_rpc_call(&mut self) -> Option<&mut RpcCall<Conn, Params, Resp, Output, Map>> {
match self {
Self::RpcCall(call) => Some(call),
_ => None,
}
}
pub const fn is_waiter(&self) -> bool {
matches!(self, Self::Waiter(_))
}
pub const fn as_waiter(&self) -> Option<&Waiter<Resp, Output, Map>> {
match self {
Self::Waiter(waiter) => Some(waiter),
_ => None,
}
}
pub fn as_mut_waiter(&mut self) -> Option<&mut Waiter<Resp, Output, Map>> {
match self {
Self::Waiter(waiter) => Some(waiter),
_ => None,
}
}
pub const fn is_boxed_future(&self) -> bool {
matches!(self, Self::BoxedFuture(_))
}
pub const fn as_boxed_future(
&self,
) -> Option<&Pin<Box<dyn Future<Output = TransportResult<Output>> + Send>>> {
match self {
Self::BoxedFuture(fut) => Some(fut),
_ => None,
}
}
pub const fn is_ready(&self) -> bool {
matches!(self, Self::Ready(_))
}
pub const fn as_ready(&self) -> Option<&TransportResult<Output>> {
match self {
Self::Ready(Some(output)) => Some(output),
Self::Ready(None) => panic!("tried to access ready value after taking"),
_ => None,
}
}
pub fn map_resp<NewOutput, NewMap>(
self,
map: NewMap,
) -> Result<ProviderCall<Conn, Params, Resp, NewOutput, NewMap>, Self>
where
NewMap: Fn(Resp) -> NewOutput + Clone,
{
match self {
Self::RpcCall(call) => Ok(ProviderCall::RpcCall(call.map_resp(map))),
Self::Waiter(waiter) => Ok(ProviderCall::Waiter(waiter.map_resp(map))),
_ => Err(self),
}
}
}
impl<Conn, Params, Resp, Output, Map> ProviderCall<Conn, &Params, Resp, Output, Map>
where
Conn: Transport + Clone,
Params: RpcParam + ToOwned,
Params::Owned: RpcParam,
Resp: RpcReturn,
Map: Fn(Resp) -> Output,
{
pub fn into_owned_params(self) -> ProviderCall<Conn, Params::Owned, Resp, Output, Map> {
match self {
Self::RpcCall(call) => ProviderCall::RpcCall(call.into_owned_params()),
_ => panic!(),
}
}
}
impl<Conn, Params, Resp> std::fmt::Debug for ProviderCall<Conn, Params, Resp>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RpcCall(call) => f.debug_tuple("RpcCall").field(call).finish(),
Self::Waiter { .. } => f.debug_struct("Waiter").finish_non_exhaustive(),
Self::BoxedFuture(_) => f.debug_struct("BoxedFuture").finish_non_exhaustive(),
Self::Ready(_) => f.debug_struct("Ready").finish_non_exhaustive(),
}
}
}
impl<Conn, Params, Resp, Output, Map> From<RpcCall<Conn, Params, Resp, Output, Map>>
for ProviderCall<Conn, Params, Resp, Output, Map>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
Map: Fn(Resp) -> Output,
{
fn from(call: RpcCall<Conn, Params, Resp, Output, Map>) -> Self {
Self::RpcCall(call)
}
}
impl<Conn, Params, Resp> From<Waiter<Resp>>
for ProviderCall<Conn, Params, Resp, Resp, fn(Resp) -> Resp>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
{
fn from(waiter: Waiter<Resp>) -> Self {
Self::Waiter(waiter)
}
}
impl<Conn, Params, Resp, Output, Map>
From<Pin<Box<dyn Future<Output = TransportResult<Output>> + Send>>>
for ProviderCall<Conn, Params, Resp, Output, Map>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
Map: Fn(Resp) -> Output,
{
fn from(fut: Pin<Box<dyn Future<Output = TransportResult<Output>> + Send>>) -> Self {
Self::BoxedFuture(fut)
}
}
impl<Conn, Params, Resp> From<oneshot::Receiver<TransportResult<Box<RawValue>>>>
for ProviderCall<Conn, Params, Resp>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
{
fn from(rx: oneshot::Receiver<TransportResult<Box<RawValue>>>) -> Self {
Waiter::from(rx).into()
}
}
impl<Conn, Params, Resp, Output, Map> Future for ProviderCall<Conn, Params, Resp, Output, Map>
where
Conn: Transport + Clone,
Params: RpcParam,
Resp: RpcReturn,
Output: 'static,
Map: Fn(Resp) -> Output,
{
type Output = TransportResult<Output>;
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
match self.as_mut().project() {
ProviderCallProj::RpcCall(call) => call.poll_unpin(cx),
ProviderCallProj::Waiter(waiter) => waiter.poll_unpin(cx),
ProviderCallProj::BoxedFuture(fut) => fut.poll_unpin(cx),
ProviderCallProj::Ready(output) => {
Poll::Ready(output.take().expect("output taken twice"))
}
}
}
}