rpc_router/rpc_message/
request.rs

1use crate::support::get_json_type;
2use crate::{RpcId, RpcRequestParsingError};
3use serde::ser::SerializeStruct;
4use serde::{Deserialize, Serializer};
5use serde_json::Value;
6
7/// The raw JSON-RPC request object, serving as the foundation for RPC routing.
8#[derive(Deserialize, Clone, Debug)]
9pub struct RpcRequest {
10	pub id: RpcId,
11	pub method: String,
12	pub params: Option<Value>,
13}
14
15impl RpcRequest {
16	pub fn new(id: impl Into<RpcId>, method: impl Into<String>, params: Option<Value>) -> Self {
17		RpcRequest {
18			id: id.into(),
19			method: method.into(),
20			params,
21		}
22	}
23}
24
25/// Custom parser (probably need to be a deserializer)
26impl RpcRequest {
27	pub fn from_value(value: Value) -> Result<RpcRequest, RpcRequestParsingError> {
28		RpcRequest::from_value_with_checks(value, RpcRequestCheckFlags::ALL)
29	}
30
31	/// Will perform the `jsonrpc: "2.0"` validation and parse the request.
32	/// If this is not desired, using the standard `serde_json::from_value` would do the parsing
33	/// and ignore `jsonrpc` property.
34	pub fn from_value_with_checks(
35		value: Value,
36		checks: RpcRequestCheckFlags,
37	) -> Result<RpcRequest, RpcRequestParsingError> {
38		// TODO: When capturing the Value, we might implement a safeguard to prevent capturing Value Object or arrays
39		//       as they can be indefinitely large. One technical solution would be to replace the value with a String,
40		//       using something like `"[object/array redacted, 'id' should be of type number, string or null]"` as the string.
41		let value_type = get_json_type(&value);
42
43		let Value::Object(mut obj) = value else {
44			return Err(RpcRequestParsingError::RequestInvalidType {
45				actual_type: value_type.to_string(),
46			});
47		};
48
49		// -- Check and remove `jsonrpc` property
50		if checks.contains(RpcRequestCheckFlags::VERSION) {
51			// -- Check and remove `jsonrpc` property
52			match obj.remove("jsonrpc") {
53				Some(version) => {
54					if version.as_str().unwrap_or_default() != "2.0" {
55						let (id_val, method) = extract_id_value_and_method(obj);
56						return Err(RpcRequestParsingError::VersionInvalid {
57							id: id_val,
58							method,
59							version,
60						});
61					}
62				}
63				None => {
64					let (id_val, method) = extract_id_value_and_method(obj);
65					return Err(RpcRequestParsingError::VersionMissing { id: id_val, method });
66				}
67			}
68		}
69
70		// -- Extract Raw Value Id for now
71		let rpc_id_value: Option<Value> = obj.remove("id");
72
73		// -- Check method presence and type
74		let method = match obj.remove("method") {
75			None => {
76				return Err(RpcRequestParsingError::MethodMissing { id: rpc_id_value });
77			}
78			Some(method_val) => match method_val {
79				Value::String(method_name) => method_name,
80				other => {
81					return Err(RpcRequestParsingError::MethodInvalidType {
82						id: rpc_id_value,
83						method: other,
84					});
85				}
86			},
87		};
88
89		// -- Process RpcId
90		// Note: here if we do not have the check_id flag, we are permissive on the rpc_id, and
91		let check_id = checks.contains(RpcRequestCheckFlags::ID);
92		let id = match rpc_id_value {
93			None => {
94				if check_id {
95					return Err(RpcRequestParsingError::IdMissing { method: Some(method) });
96				} else {
97					RpcId::Null
98				}
99			}
100			Some(id_value) => match RpcId::from_value(id_value) {
101				Ok(rpc_id) => rpc_id,
102				Err(err) => {
103					if check_id {
104						return Err(err);
105					} else {
106						RpcId::Null
107					}
108				}
109			},
110		};
111
112		// -- Extract params (can be absent, which is valid)
113		let params = obj.get_mut("params").map(Value::take);
114
115		Ok(RpcRequest { id, method, params })
116	}
117}
118
119// region:    --- Serialize Custom
120
121impl serde::Serialize for RpcRequest {
122	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123	where
124		S: Serializer,
125	{
126		// Determine the number of fields: jsonrpc, id, method are always present. params is optional.
127		let mut field_count = 3;
128		if self.params.is_some() {
129			field_count += 1;
130		}
131
132		let mut state = serializer.serialize_struct("RpcRequest", field_count)?;
133
134		// Always add "jsonrpc": "2.0"
135		state.serialize_field("jsonrpc", "2.0")?;
136
137		state.serialize_field("id", &self.id)?;
138		state.serialize_field("method", &self.method)?;
139
140		// Serialize params only if it's Some
141		if let Some(params) = &self.params {
142			state.serialize_field("params", params)?;
143		}
144
145		state.end()
146	}
147}
148
149// endregion: --- Serialize Custom
150
151bitflags::bitflags! {
152	/// Represents a set of flags.
153	#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
154	pub struct RpcRequestCheckFlags: u32 {
155		/// Check the `jsonrpc = "2.0"` property
156		const VERSION = 0b00000001;
157		/// Check that `id` property is a valid rpc id (string, number, or null)
158		/// NOTE: Does not support floating number (although the spec does)
159		const ID = 0b00000010;
160
161		/// Check all, te ID and VERSION
162		const ALL = Self::VERSION.bits() | Self::ID.bits();
163	}
164}
165
166// region:    --- Support
167
168// Extract (remove) the id and method.
169fn extract_id_value_and_method(mut obj: serde_json::Map<String, Value>) -> (Option<Value>, Option<String>) {
170	let id = obj.remove("id");
171	// for now be permisive with the method name, so as_str
172	let method = obj.remove("method").and_then(|v| v.as_str().map(|s| s.to_string()));
173	(id, method)
174}
175
176/// Convenient TryFrom, and will execute the Request::from_value,
177/// which will perform the version validation.
178impl TryFrom<Value> for RpcRequest {
179	type Error = RpcRequestParsingError;
180	fn try_from(value: Value) -> Result<RpcRequest, RpcRequestParsingError> {
181		RpcRequest::from_value(value)
182	}
183}
184
185// endregion: --- Support