1use alloy_json_rpc::{RequestMeta, RpcRecv, RpcSend};
2use alloy_rpc_client::{RpcCall, Waiter};
3use alloy_transport::TransportResult;
4use futures::FutureExt;
5use http::{HeaderMap, HeaderName, HeaderValue};
6use pin_project::pin_project;
7use serde_json::value::RawValue;
8use std::{
9 future::Future,
10 pin::Pin,
11 task::{self, Poll},
12};
13use tokio::sync::oneshot;
14
15#[cfg(not(target_family = "wasm"))]
16pub type BoxedFut<Output> = Pin<Box<dyn Future<Output = TransportResult<Output>> + Send>>;
18
19#[cfg(target_family = "wasm")]
20pub type BoxedFut<Output> = Pin<Box<dyn Future<Output = TransportResult<Output>>>>;
22#[pin_project(project = ProviderCallProj)]
33pub enum ProviderCall<Params, Resp, Output = Resp, Map = fn(Resp) -> Output>
34where
35 Params: RpcSend,
36 Resp: RpcRecv,
37 Map: Fn(Resp) -> Output,
38{
39 RpcCall(RpcCall<Params, Resp, Output, Map>),
41 Waiter(Waiter<Resp, Output, Map>),
43 BoxedFuture(BoxedFut<Output>),
45 Ready(Option<TransportResult<Output>>),
47}
48
49impl<Params, Resp, Output, Map> ProviderCall<Params, Resp, Output, Map>
50where
51 Params: RpcSend,
52 Resp: RpcRecv,
53 Map: Fn(Resp) -> Output,
54{
55 pub const fn ready(output: TransportResult<Output>) -> Self {
57 Self::Ready(Some(output))
58 }
59
60 pub const fn is_rpc_call(&self) -> bool {
62 matches!(self, Self::RpcCall(_))
63 }
64
65 pub const fn as_rpc_call(&self) -> Option<&RpcCall<Params, Resp, Output, Map>> {
67 match self {
68 Self::RpcCall(call) => Some(call),
69 _ => None,
70 }
71 }
72
73 pub const fn as_mut_rpc_call(&mut self) -> Option<&mut RpcCall<Params, Resp, Output, Map>> {
75 match self {
76 Self::RpcCall(call) => Some(call),
77 _ => None,
78 }
79 }
80
81 pub const fn is_waiter(&self) -> bool {
83 matches!(self, Self::Waiter(_))
84 }
85
86 pub const fn as_waiter(&self) -> Option<&Waiter<Resp, Output, Map>> {
88 match self {
89 Self::Waiter(waiter) => Some(waiter),
90 _ => None,
91 }
92 }
93
94 pub const fn as_mut_waiter(&mut self) -> Option<&mut Waiter<Resp, Output, Map>> {
96 match self {
97 Self::Waiter(waiter) => Some(waiter),
98 _ => None,
99 }
100 }
101
102 pub const fn is_boxed_future(&self) -> bool {
104 matches!(self, Self::BoxedFuture(_))
105 }
106
107 pub const fn as_boxed_future(&self) -> Option<&BoxedFut<Output>> {
109 match self {
110 Self::BoxedFuture(fut) => Some(fut),
111 _ => None,
112 }
113 }
114
115 pub const fn is_ready(&self) -> bool {
117 matches!(self, Self::Ready(_))
118 }
119
120 pub const fn as_ready(&self) -> Option<&TransportResult<Output>> {
126 match self {
127 Self::Ready(Some(output)) => Some(output),
128 Self::Ready(None) => panic!("tried to access ready value after taking"),
129 _ => None,
130 }
131 }
132
133 pub fn map_resp<NewOutput, NewMap>(
149 self,
150 map: NewMap,
151 ) -> Result<ProviderCall<Params, Resp, NewOutput, NewMap>, Self>
152 where
153 NewMap: Fn(Resp) -> NewOutput + Clone,
154 {
155 match self {
156 Self::RpcCall(call) => Ok(ProviderCall::RpcCall(call.map_resp(map))),
157 Self::Waiter(waiter) => Ok(ProviderCall::Waiter(waiter.map_resp(map))),
158 _ => Err(self),
159 }
160 }
161
162 pub fn map_meta(self, f: impl FnOnce(RequestMeta) -> RequestMeta) -> Result<Self, Self> {
190 match self {
191 Self::RpcCall(call) => Ok(Self::RpcCall(call.map_meta(f))),
192 _ => Err(self),
193 }
194 }
195
196 pub fn with_headers(self, headers: HeaderMap) -> Result<Self, Self> {
201 self.map_meta(|mut meta| {
202 meta.headers_mut().extend(headers);
203 meta
204 })
205 }
206
207 pub fn with_header(self, name: HeaderName, value: HeaderValue) -> Result<Self, Self> {
212 self.map_meta(|mut meta| {
213 meta.headers_mut().insert(name, value);
214 meta
215 })
216 }
217}
218
219impl<Params, Resp, Output, Map> ProviderCall<&Params, Resp, Output, Map>
220where
221 Params: RpcSend + ToOwned,
222 Params::Owned: RpcSend,
223 Resp: RpcRecv,
224 Map: Fn(Resp) -> Output,
225{
226 pub fn into_owned_params(self) -> ProviderCall<Params::Owned, Resp, Output, Map> {
232 match self {
233 Self::RpcCall(call) => ProviderCall::RpcCall(call.into_owned_params()),
234 _ => panic!(),
235 }
236 }
237}
238
239impl<Params, Resp> std::fmt::Debug for ProviderCall<Params, Resp>
240where
241 Params: RpcSend,
242 Resp: RpcRecv,
243{
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 Self::RpcCall(call) => f.debug_tuple("RpcCall").field(call).finish(),
247 Self::Waiter { .. } => f.debug_struct("Waiter").finish_non_exhaustive(),
248 Self::BoxedFuture(_) => f.debug_struct("BoxedFuture").finish_non_exhaustive(),
249 Self::Ready(_) => f.debug_struct("Ready").finish_non_exhaustive(),
250 }
251 }
252}
253
254impl<Params, Resp, Output, Map> From<RpcCall<Params, Resp, Output, Map>>
255 for ProviderCall<Params, Resp, Output, Map>
256where
257 Params: RpcSend,
258 Resp: RpcRecv,
259 Map: Fn(Resp) -> Output,
260{
261 fn from(call: RpcCall<Params, Resp, Output, Map>) -> Self {
262 Self::RpcCall(call)
263 }
264}
265
266impl<Params, Resp> From<Waiter<Resp>> for ProviderCall<Params, Resp, Resp, fn(Resp) -> Resp>
267where
268 Params: RpcSend,
269 Resp: RpcRecv,
270{
271 fn from(waiter: Waiter<Resp>) -> Self {
272 Self::Waiter(waiter)
273 }
274}
275
276impl<Params, Resp, Output, Map> From<BoxedFut<Output>> for ProviderCall<Params, Resp, Output, Map>
277where
278 Params: RpcSend,
279 Resp: RpcRecv,
280 Map: Fn(Resp) -> Output,
281{
282 fn from(fut: BoxedFut<Output>) -> Self {
283 Self::BoxedFuture(fut)
284 }
285}
286
287impl<Params, Resp> From<oneshot::Receiver<TransportResult<Box<RawValue>>>>
288 for ProviderCall<Params, Resp>
289where
290 Params: RpcSend,
291 Resp: RpcRecv,
292{
293 fn from(rx: oneshot::Receiver<TransportResult<Box<RawValue>>>) -> Self {
294 Waiter::from(rx).into()
295 }
296}
297
298impl<Params, Resp, Output, Map> Future for ProviderCall<Params, Resp, Output, Map>
299where
300 Params: RpcSend,
301 Resp: RpcRecv,
302 Output: 'static,
303 Map: Fn(Resp) -> Output,
304{
305 type Output = TransportResult<Output>;
306
307 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
308 match self.as_mut().project() {
309 ProviderCallProj::RpcCall(call) => call.poll_unpin(cx),
310 ProviderCallProj::Waiter(waiter) => waiter.poll_unpin(cx),
311 ProviderCallProj::BoxedFuture(fut) => fut.poll_unpin(cx),
312 ProviderCallProj::Ready(output) => {
313 Poll::Ready(output.take().expect("output taken twice"))
314 }
315 }
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use alloy_rpc_client::{ClientBuilder, NoParams};
323 use alloy_transport::mock::{Asserter, MockTransport};
324 use http::HeaderValue;
325
326 #[test]
327 fn map_meta_updates_rpc_call_metadata() {
328 let client = ClientBuilder::default().transport(MockTransport::new(Asserter::new()), true);
329 let call: ProviderCall<NoParams, u64> = client.request_noparams("test_method").into();
330
331 let call = call
332 .map_meta(|mut meta| {
333 meta.headers_mut().insert("x-api-key", HeaderValue::from_static("secret"));
334 meta
335 })
336 .expect("call is an RPC call");
337
338 assert_eq!(
339 call.as_rpc_call().unwrap().request().meta.headers().unwrap().get("x-api-key"),
340 Some(&HeaderValue::from_static("secret"))
341 );
342 }
343
344 #[test]
345 fn map_meta_returns_non_rpc_call() {
346 let call = ProviderCall::<NoParams, u64>::ready(Ok(1));
347 assert!(call.map_meta(std::convert::identity).is_err());
348 }
349
350 #[test]
351 fn with_headers_updates_rpc_call_headers() {
352 let client = ClientBuilder::default().transport(MockTransport::new(Asserter::new()), true);
353 let call: ProviderCall<NoParams, u64> = client.request_noparams("test_method").into();
354 let mut headers = HeaderMap::new();
355 headers.insert("x-api-key", HeaderValue::from_static("secret"));
356
357 let call = call.with_headers(headers).expect("call is an RPC call");
358
359 assert_eq!(
360 call.as_rpc_call().unwrap().request().meta.headers().unwrap().get("x-api-key"),
361 Some(&HeaderValue::from_static("secret"))
362 );
363 }
364
365 #[test]
366 fn with_header_updates_rpc_call_header() {
367 let client = ClientBuilder::default().transport(MockTransport::new(Asserter::new()), true);
368 let call: ProviderCall<NoParams, u64> = client.request_noparams("test_method").into();
369
370 let call = call
371 .with_header(HeaderName::from_static("x-api-key"), HeaderValue::from_static("secret"))
372 .expect("call is an RPC call");
373
374 assert_eq!(
375 call.as_rpc_call().unwrap().request().meta.headers().unwrap().get("x-api-key"),
376 Some(&HeaderValue::from_static("secret"))
377 );
378 }
379}