Skip to main content

roboplc_rpc/
request.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    de_validate_version,
5    response::{HandlerResponse, Response},
6    serialize_version, Id, RpcError, RpcErrorKind, String, VERSION_HEADER,
7};
8
9#[cfg(feature = "canonical")]
10use crate::{ERR_INVALID_PROTOCOL_VERSION, JSONRPC_VERSION};
11
12#[derive(Serialize, Deserialize, Debug)]
13#[serde(deny_unknown_fields)]
14/// JSON-RPC Request object
15pub struct Request<M> {
16    #[serde(
17        default,
18        deserialize_with = "de_validate_version",
19        serialize_with = "serialize_version",
20        skip_serializing_if = "Option::is_none"
21    )]
22    jsonrpc: Option<()>,
23    #[cfg_attr(
24        feature = "canonical",
25        serde(skip_serializing_if = "Option::is_none", alias = "i")
26    )]
27    #[cfg_attr(
28        not(feature = "canonical"),
29        serde(rename = "i", skip_serializing_if = "Option::is_none")
30    )]
31    pub(crate) id: Option<Id>,
32    #[cfg_attr(feature = "std", serde(flatten))]
33    #[cfg_attr(not(feature = "std"), serde(rename = "p"))]
34    pub(crate) method: M,
35}
36
37impl<'a, M> Request<M>
38where
39    M: Serialize + Deserialize<'a>,
40{
41    /// Create a new Request object with the given method with no ID (no response expected)
42    pub fn new0(method: M) -> Request<M> {
43        Request {
44            jsonrpc: VERSION_HEADER,
45            id: None,
46            method,
47        }
48    }
49    /// Create a new Request object with the given method and ID
50    pub fn new(id: Id, method: M) -> Request<M> {
51        Request {
52            jsonrpc: VERSION_HEADER,
53            id: Some(id),
54            method,
55        }
56    }
57    /// Split the Request object into its parts (useful for 3rd party serialization)
58    pub fn into_parts(self) -> (Option<Id>, M) {
59        (self.id, self.method)
60    }
61    /// Combine the parts into a Request object (useful for 3rd party de-serialization)
62    pub fn from_parts(id: Option<Id>, method: M) -> Request<M> {
63        Request {
64            jsonrpc: VERSION_HEADER,
65            id,
66            method,
67        }
68    }
69}
70
71#[allow(clippy::module_name_repetitions)]
72#[derive(Deserialize, Debug)]
73/// An object to try de-serializing an invalid request to determine the error
74pub struct InvalidRequest<'a> {
75    #[allow(dead_code)]
76    jsonrpc: Option<&'a str>,
77    id: Option<Id>,
78}
79
80impl InvalidRequest<'_> {
81    /// Convert the InvalidRequest object into a Response object with the given error message
82    pub fn into_response<R>(self, error: String) -> Option<Response<R>> {
83        if let Some(id) = self.id {
84            #[cfg(feature = "canonical")]
85            let (code, message) = if let Some(jsonrpc) = self.jsonrpc {
86                if jsonrpc == JSONRPC_VERSION {
87                    (RpcErrorKind::MethodNotFound, Some(error))
88                } else {
89                    (
90                        RpcErrorKind::InvalidRequest,
91                        #[allow(clippy::unnecessary_fallible_conversions)]
92                        ERR_INVALID_PROTOCOL_VERSION.try_into().ok(),
93                    )
94                }
95            } else {
96                (RpcErrorKind::InvalidRequest, None)
97            };
98            #[cfg(not(feature = "canonical"))]
99            let (code, message) = (RpcErrorKind::MethodNotFound, Some(error));
100            Some(Response::from_handler_response(
101                id,
102                HandlerResponse::Err(RpcError {
103                    kind: code,
104                    message,
105                }),
106            ))
107        } else {
108            None
109        }
110    }
111}