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