alloy_json_rpc/
request.rs

1use crate::{common::Id, RpcBorrow, RpcSend};
2use alloy_primitives::{keccak256, B256};
3use http::Extensions;
4use serde::{
5    de::{DeserializeOwned, MapAccess},
6    ser::SerializeMap,
7    Deserialize, Serialize,
8};
9use serde_json::value::RawValue;
10use std::{borrow::Cow, marker::PhantomData, mem::MaybeUninit};
11
12/// `RequestMeta` contains the [`Id`] and method name of a request.
13#[derive(Clone, Debug)]
14pub struct RequestMeta {
15    /// The method name.
16    pub method: Cow<'static, str>,
17    /// The request ID.
18    pub id: Id,
19    /// Whether the request is a subscription, other than `eth_subscribe`.
20    is_subscription: bool,
21    /// Optional extensions for the request that can be used by middleware
22    /// or other components to attach additional metadata.
23    extensions: Extensions,
24}
25
26impl RequestMeta {
27    /// Create a new `RequestMeta`.
28    pub fn new(method: Cow<'static, str>, id: Id) -> Self {
29        Self { method, id, is_subscription: false, extensions: Extensions::new() }
30    }
31
32    /// Returns `true` if the request is a subscription.
33    pub fn is_subscription(&self) -> bool {
34        self.is_subscription || self.method == "eth_subscribe"
35    }
36
37    /// Indicates that the request is a non-standard subscription (i.e. not
38    /// "eth_subscribe").
39    pub const fn set_is_subscription(&mut self) {
40        self.set_subscription_status(true);
41    }
42
43    /// Setter for `is_subscription`. Indicates to RPC clients that the request
44    /// triggers a stream of notifications.
45    pub const fn set_subscription_status(&mut self, sub: bool) {
46        self.is_subscription = sub;
47    }
48
49    /// Returns a reference to the request extensions.
50    ///
51    /// These can be used to attach additional metadata to the request
52    /// that can be used by middleware or other components.
53    pub const fn extensions(&self) -> &Extensions {
54        &self.extensions
55    }
56
57    /// Returns a mutable reference to the request extensions.
58    ///
59    /// These can be used to attach additional metadata to the request
60    /// that can be used by middleware or other components.
61    pub const fn extensions_mut(&mut self) -> &mut Extensions {
62        &mut self.extensions
63    }
64
65    /// Returns a reference to the request headers, if any.
66    pub fn headers(&self) -> Option<&http::HeaderMap> {
67        self.extensions.get::<http::HeaderMap>()
68    }
69
70    /// Returns a mutable reference to the request headers, inserting an empty
71    /// header map if one does not already exist.
72    pub fn headers_mut(&mut self) -> &mut http::HeaderMap {
73        self.extensions.get_or_insert_default::<http::HeaderMap>()
74    }
75}
76
77impl PartialEq for RequestMeta {
78    fn eq(&self, other: &Self) -> bool {
79        self.method == other.method
80            && self.id == other.id
81            && self.is_subscription == other.is_subscription
82    }
83}
84
85impl Eq for RequestMeta {}
86
87/// A JSON-RPC 2.0 request object.
88///
89/// This is a generic type that can be used to represent any JSON-RPC request.
90/// The `Params` type parameter is used to represent the parameters of the
91/// request, and the `method` field is used to represent the method name.
92///
93/// ### Note
94///
95/// The value of `method` should be known at compile time.
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct Request<Params> {
98    /// The request metadata (ID and method).
99    pub meta: RequestMeta,
100    /// The request parameters.
101    pub params: Params,
102}
103
104impl<Params> Request<Params> {
105    /// Create a new `Request`.
106    pub fn new(method: impl Into<Cow<'static, str>>, id: Id, params: Params) -> Self {
107        Self { meta: RequestMeta::new(method.into(), id), params }
108    }
109
110    /// Returns `true` if the request is a subscription.
111    pub fn is_subscription(&self) -> bool {
112        self.meta.is_subscription()
113    }
114
115    /// Indicates that the request is a non-standard subscription (i.e. not
116    /// "eth_subscribe").
117    pub const fn set_is_subscription(&mut self) {
118        self.meta.set_is_subscription()
119    }
120
121    /// Setter for `is_subscription`. Indicates to RPC clients that the request
122    /// triggers a stream of notifications.
123    pub const fn set_subscription_status(&mut self, sub: bool) {
124        self.meta.set_subscription_status(sub);
125    }
126
127    /// Change type of the request parameters.
128    pub fn map_params<NewParams>(
129        self,
130        map: impl FnOnce(Params) -> NewParams,
131    ) -> Request<NewParams> {
132        Request { meta: self.meta, params: map(self.params) }
133    }
134
135    /// Change the metadata of the request.
136    pub fn map_meta<F>(self, f: F) -> Self
137    where
138        F: FnOnce(RequestMeta) -> RequestMeta,
139    {
140        Self { meta: f(self.meta), params: self.params }
141    }
142}
143
144/// A [`Request`] that has been partially serialized.
145///
146/// The request parameters have been serialized, and are represented as a boxed [`RawValue`]. This
147/// is useful for collections containing many requests, as it erases the `Param` type. It can be
148/// created with [`Request::box_params()`].
149///
150/// See the [top-level docs] for more info.
151///
152/// [top-level docs]: crate
153pub type PartiallySerializedRequest = Request<Box<RawValue>>;
154
155impl<Params> Request<Params>
156where
157    Params: RpcSend,
158{
159    /// Serialize the request parameters as a boxed [`RawValue`].
160    ///
161    /// # Panics
162    ///
163    /// If serialization of the params fails.
164    pub fn box_params(self) -> PartiallySerializedRequest {
165        Request { meta: self.meta, params: serde_json::value::to_raw_value(&self.params).unwrap() }
166    }
167
168    /// Serialize the request, including the request parameters.
169    pub fn serialize(self) -> serde_json::Result<SerializedRequest> {
170        let request = serde_json::value::to_raw_value(&self)?;
171        Ok(SerializedRequest { meta: self.meta, request })
172    }
173}
174
175impl<Params> Request<&Params>
176where
177    Params: ToOwned,
178    Params::Owned: RpcSend,
179{
180    /// Clone the request, including the request parameters.
181    pub fn into_owned_params(self) -> Request<Params::Owned> {
182        Request { meta: self.meta, params: self.params.to_owned() }
183    }
184}
185
186impl<'a, Params> Request<Params>
187where
188    Params: AsRef<RawValue> + 'a,
189{
190    /// Attempt to deserialize the params.
191    ///
192    /// To borrow from the params via the deserializer, use
193    /// [`Request::try_borrow_params_as`].
194    ///
195    /// # Returns
196    /// - `Ok(T)` if the params can be deserialized as `T`
197    /// - `Err(e)` if the params cannot be deserialized as `T`
198    pub fn try_params_as<T: DeserializeOwned>(&self) -> serde_json::Result<T> {
199        serde_json::from_str(self.params.as_ref().get())
200    }
201
202    /// Attempt to deserialize the params, borrowing from the params
203    ///
204    /// # Returns
205    /// - `Ok(T)` if the params can be deserialized as `T`
206    /// - `Err(e)` if the params cannot be deserialized as `T`
207    pub fn try_borrow_params_as<T: Deserialize<'a>>(&'a self) -> serde_json::Result<T> {
208        serde_json::from_str(self.params.as_ref().get())
209    }
210}
211
212// manually implemented to avoid adding a type for the protocol-required
213// `jsonrpc` field
214impl<Params> Serialize for Request<Params>
215where
216    Params: RpcSend,
217{
218    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
219    where
220        S: serde::Serializer,
221    {
222        let sized_params = std::mem::size_of::<Params>() != 0;
223
224        let mut map = serializer.serialize_map(Some(3 + sized_params as usize))?;
225        map.serialize_entry("method", &self.meta.method[..])?;
226
227        // Params may be omitted if it is 0-sized
228        if sized_params {
229            map.serialize_entry("params", &self.params)?;
230        }
231
232        map.serialize_entry("id", &self.meta.id)?;
233        map.serialize_entry("jsonrpc", "2.0")?;
234        map.end()
235    }
236}
237
238impl<'de, Params> Deserialize<'de> for Request<Params>
239where
240    Params: RpcBorrow<'de>,
241{
242    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243    where
244        D: serde::Deserializer<'de>,
245    {
246        struct Visitor<Params>(PhantomData<Params>);
247        impl<'de, Params> serde::de::Visitor<'de> for Visitor<Params>
248        where
249            Params: RpcBorrow<'de>,
250        {
251            type Value = Request<Params>;
252
253            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254                write!(
255                    formatter,
256                    "a JSON-RPC 2.0 request object with params of type {}",
257                    std::any::type_name::<Params>()
258                )
259            }
260
261            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
262            where
263                A: MapAccess<'de>,
264            {
265                let mut id = None;
266                let mut params = None;
267                let mut method = None;
268                let mut jsonrpc = None;
269
270                while let Some(key) = map.next_key()? {
271                    match key {
272                        "id" => {
273                            if id.is_some() {
274                                return Err(serde::de::Error::duplicate_field("id"));
275                            }
276                            id = Some(map.next_value()?);
277                        }
278                        "params" => {
279                            if params.is_some() {
280                                return Err(serde::de::Error::duplicate_field("params"));
281                            }
282                            params = Some(map.next_value()?);
283                        }
284                        "method" => {
285                            if method.is_some() {
286                                return Err(serde::de::Error::duplicate_field("method"));
287                            }
288                            method = Some(map.next_value()?);
289                        }
290                        "jsonrpc" => {
291                            let version: String = map.next_value()?;
292                            if version != "2.0" {
293                                return Err(serde::de::Error::custom(format!(
294                                    "unsupported JSON-RPC version: {version}"
295                                )));
296                            }
297                            jsonrpc = Some(());
298                        }
299                        other => {
300                            return Err(serde::de::Error::unknown_field(
301                                other,
302                                &["id", "params", "method", "jsonrpc"],
303                            ));
304                        }
305                    }
306                }
307                if jsonrpc.is_none() {
308                    return Err(serde::de::Error::missing_field("jsonrpc"));
309                }
310                if method.is_none() {
311                    return Err(serde::de::Error::missing_field("method"));
312                }
313
314                if params.is_none() {
315                    if std::mem::size_of::<Params>() == 0 {
316                        // SAFETY: params is a ZST, so it's safe to fail to initialize it
317                        unsafe { params = Some(MaybeUninit::<Params>::zeroed().assume_init()) }
318                    } else {
319                        return Err(serde::de::Error::missing_field("params"));
320                    }
321                }
322
323                Ok(Request {
324                    meta: RequestMeta::new(method.unwrap(), id.unwrap_or(Id::None)),
325                    params: params.unwrap(),
326                })
327            }
328        }
329
330        deserializer.deserialize_map(Visitor(PhantomData))
331    }
332}
333
334/// A JSON-RPC 2.0 request object that has been serialized, with its [`Id`] and
335/// method preserved.
336///
337/// This struct is used to represent a request that has been serialized, but
338/// not yet sent. It is used by RPC clients to build batch requests and manage
339/// in-flight requests.
340#[derive(Clone, Debug)]
341pub struct SerializedRequest {
342    meta: RequestMeta,
343    request: Box<RawValue>,
344}
345
346impl<Params> TryFrom<Request<Params>> for SerializedRequest
347where
348    Params: RpcSend,
349{
350    type Error = serde_json::Error;
351
352    fn try_from(value: Request<Params>) -> Result<Self, Self::Error> {
353        value.serialize()
354    }
355}
356
357impl SerializedRequest {
358    /// Returns the request metadata (ID and Method).
359    pub const fn meta(&self) -> &RequestMeta {
360        &self.meta
361    }
362
363    /// Returns a mutable reference to the request metadata (ID and Method).
364    pub const fn meta_mut(&mut self) -> &mut RequestMeta {
365        &mut self.meta
366    }
367
368    /// Returns a reference to the request headers, if any.
369    pub fn headers(&self) -> Option<&http::HeaderMap> {
370        self.meta.headers()
371    }
372
373    /// Returns a mutable reference to the request headers, inserting an empty
374    /// header map if one does not already exist.
375    pub fn headers_mut(&mut self) -> &mut http::HeaderMap {
376        self.meta.headers_mut()
377    }
378
379    /// Returns the request ID.
380    pub const fn id(&self) -> &Id {
381        &self.meta.id
382    }
383
384    /// Returns the request method.
385    pub fn method(&self) -> &str {
386        &self.meta.method
387    }
388
389    /// Mark the request as a non-standard subscription (i.e. not
390    /// `eth_subscribe`)
391    pub const fn set_is_subscription(&mut self) {
392        self.meta.set_is_subscription();
393    }
394
395    /// Returns `true` if the request is a subscription.
396    pub fn is_subscription(&self) -> bool {
397        self.meta.is_subscription()
398    }
399
400    /// Returns the serialized request.
401    pub const fn serialized(&self) -> &RawValue {
402        &self.request
403    }
404
405    /// Consume the serialized request, returning the underlying [`RawValue`].
406    pub fn into_serialized(self) -> Box<RawValue> {
407        self.request
408    }
409
410    /// Consumes the serialized request, returning the underlying
411    /// [`RequestMeta`] and the [`RawValue`].
412    pub fn decompose(self) -> (RequestMeta, Box<RawValue>) {
413        (self.meta, self.request)
414    }
415
416    /// Take the serialized request, consuming the [`SerializedRequest`].
417    pub fn take_request(self) -> Box<RawValue> {
418        self.request
419    }
420
421    /// Get a reference to the serialized request's params.
422    ///
423    /// This partially deserializes the request, and should be avoided if
424    /// possible.
425    pub fn params(&self) -> Option<&RawValue> {
426        #[derive(Deserialize)]
427        struct Req<'a> {
428            #[serde(borrow)]
429            params: Option<&'a RawValue>,
430        }
431
432        let req: Req<'_> = serde_json::from_str(self.request.get()).unwrap();
433        req.params
434    }
435
436    /// Get the hash of the serialized request's params.
437    ///
438    /// This partially deserializes the request, and should be avoided if
439    /// possible.
440    pub fn params_hash(&self) -> B256 {
441        self.params().map_or_else(|| keccak256(""), |params| keccak256(params.get()))
442    }
443}
444
445impl Serialize for SerializedRequest {
446    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
447    where
448        S: serde::Serializer,
449    {
450        self.request.serialize(serializer)
451    }
452}
453
454#[cfg(test)]
455mod test {
456    use super::*;
457    use crate::RpcObject;
458
459    fn test_inner<T: RpcObject + PartialEq>(t: T) {
460        let ser = serde_json::to_string(&t).unwrap();
461        let de: T = serde_json::from_str(&ser).unwrap();
462        let reser = serde_json::to_string(&de).unwrap();
463        assert_eq!(de, t, "deser error for {}", std::any::type_name::<T>());
464        assert_eq!(ser, reser, "reser error for {}", std::any::type_name::<T>());
465    }
466
467    #[test]
468    fn test_ser_deser() {
469        test_inner(Request::<()>::new("test", 1.into(), ()));
470        test_inner(Request::<u64>::new("test", "hello".to_string().into(), 1));
471        test_inner(Request::<String>::new("test", Id::None, "test".to_string()));
472        test_inner(Request::<Vec<u64>>::new("test", u64::MAX.into(), vec![1, 2, 3]));
473    }
474}