jsonrpsee_types/
request.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26
27//! Types to handle JSON-RPC requests according to the [spec](https://www.jsonrpc.org/specification#request-object).
28//! Some types come with a "*Ser" variant that implements [`serde::Serialize`]; these are used in the client.
29
30use std::borrow::Cow;
31
32use crate::{
33	params::{Id, TwoPointZero},
34	Params,
35};
36use http::Extensions;
37use serde::{Deserialize, Serialize};
38use serde_json::value::RawValue;
39
40/// JSON-RPC request object as defined in the [spec](https://www.jsonrpc.org/specification#request-object).
41#[derive(Serialize, Deserialize, Debug, Clone)]
42pub struct Request<'a> {
43	/// JSON-RPC version.
44	pub jsonrpc: TwoPointZero,
45	/// Request ID
46	#[serde(borrow)]
47	pub id: Id<'a>,
48	/// Name of the method to be invoked.
49	#[serde(borrow)]
50	pub method: Cow<'a, str>,
51	/// Parameter values of the request.
52	#[serde(borrow)]
53	pub params: Option<Cow<'a, RawValue>>,
54	/// The request's extensions.
55	#[serde(skip)]
56	pub extensions: Extensions,
57}
58
59impl<'a> Request<'a> {
60	/// Create a new [`Request`].
61	pub fn new(method: Cow<'a, str>, params: Option<&'a RawValue>, id: Id<'a>) -> Self {
62		Self { jsonrpc: TwoPointZero, id, method, params: params.map(Cow::Borrowed), extensions: Extensions::new() }
63	}
64
65	/// Get the ID of the request.
66	pub fn id(&self) -> Id<'a> {
67		self.id.clone()
68	}
69
70	/// Get the method name of the request.
71	pub fn method_name(&self) -> &str {
72		&self.method
73	}
74
75	/// Get the params of the request.
76	pub fn params(&self) -> Params {
77		Params::new(self.params.as_ref().map(|p| RawValue::get(p)))
78	}
79
80	/// Returns a reference to the associated extensions.
81	pub fn extensions(&self) -> &Extensions {
82		&self.extensions
83	}
84
85	/// Returns a reference to the associated extensions.
86	pub fn extensions_mut(&mut self) -> &mut Extensions {
87		&mut self.extensions
88	}
89}
90
91/// JSON-RPC Invalid request as defined in the [spec](https://www.jsonrpc.org/specification#request-object).
92#[derive(Deserialize, Debug, PartialEq, Eq)]
93pub struct InvalidRequest<'a> {
94	/// Request ID
95	#[serde(borrow)]
96	pub id: Id<'a>,
97}
98
99/// JSON-RPC notification (a request object without a request ID) as defined in the
100/// [spec](https://www.jsonrpc.org/specification#request-object).
101#[derive(Serialize, Deserialize, Debug, Clone)]
102pub struct Notification<'a, T> {
103	/// JSON-RPC version.
104	pub jsonrpc: TwoPointZero,
105	/// Name of the method to be invoked.
106	#[serde(borrow)]
107	pub method: Cow<'a, str>,
108	/// Parameter values of the request.
109	pub params: T,
110}
111
112impl<'a, T> Notification<'a, T> {
113	/// Create a new [`Notification`].
114	pub fn new(method: Cow<'a, str>, params: T) -> Self {
115		Self { jsonrpc: TwoPointZero, method, params }
116	}
117}
118
119/// Serializable [JSON-RPC object](https://www.jsonrpc.org/specification#request-object).
120#[derive(Serialize, Debug, Clone)]
121pub struct RequestSer<'a> {
122	/// JSON-RPC version.
123	pub jsonrpc: TwoPointZero,
124	/// Request ID
125	pub id: Id<'a>,
126	/// Name of the method to be invoked.
127	// NOTE: as this type only implements serialize `#[serde(borrow)]` is not needed.
128	pub method: Cow<'a, str>,
129	/// Parameter values of the request.
130	#[serde(skip_serializing_if = "Option::is_none")]
131	pub params: Option<Cow<'a, RawValue>>,
132}
133
134impl<'a> RequestSer<'a> {
135	/// Create a borrowed serializable JSON-RPC method call.
136	pub fn borrowed(id: &'a Id<'a>, method: &'a impl AsRef<str>, params: Option<&'a RawValue>) -> Self {
137		Self {
138			jsonrpc: TwoPointZero,
139			id: id.clone(),
140			method: method.as_ref().into(),
141			params: params.map(Cow::Borrowed),
142		}
143	}
144
145	/// Create a owned serializable JSON-RPC method call.
146	pub fn owned(id: Id<'a>, method: impl Into<String>, params: Option<Box<RawValue>>) -> Self {
147		Self { jsonrpc: TwoPointZero, id, method: method.into().into(), params: params.map(Cow::Owned) }
148	}
149}
150
151/// Serializable [JSON-RPC notification object](https://www.jsonrpc.org/specification#request-object).
152#[derive(Serialize, Debug, Clone)]
153pub struct NotificationSer<'a> {
154	/// JSON-RPC version.
155	pub jsonrpc: TwoPointZero,
156	/// Name of the method to be invoked.
157	// NOTE: as this type only implements serialize `#[serde(borrow)]` is not needed.
158	pub method: Cow<'a, str>,
159	/// Parameter values of the request.
160	#[serde(skip_serializing_if = "Option::is_none")]
161	pub params: Option<Cow<'a, RawValue>>,
162}
163
164impl<'a> NotificationSer<'a> {
165	/// Create a borrowed serializable JSON-RPC notification.
166	pub fn borrowed(method: &'a impl AsRef<str>, params: Option<&'a RawValue>) -> Self {
167		Self { jsonrpc: TwoPointZero, method: method.as_ref().into(), params: params.map(Cow::Borrowed) }
168	}
169
170	/// Create an owned serializable JSON-RPC notification.
171	pub fn owned(method: impl Into<String>, params: Option<Box<RawValue>>) -> Self {
172		Self { jsonrpc: TwoPointZero, method: method.into().into(), params: params.map(Cow::Owned) }
173	}
174}
175
176#[cfg(test)]
177mod test {
178	use super::{Cow, Id, InvalidRequest, Notification, NotificationSer, Request, RequestSer, TwoPointZero};
179	use serde_json::value::RawValue;
180
181	fn assert_request<'a>(request: Request<'a>, id: Id<'a>, method: &str, params: Option<&str>) {
182		assert_eq!(request.jsonrpc, TwoPointZero);
183		assert_eq!(request.id, id);
184		assert_eq!(request.method, method);
185		assert_eq!(request.params.as_ref().map(|p| RawValue::get(p)), params);
186	}
187
188	/// Checks that we can deserialize the object with or without non-mandatory fields.
189	#[test]
190	fn deserialize_call() {
191		let method = "subtract";
192		let params = "[42, 23]";
193
194		let test_vector = vec![
195			// With all fields set.
196			(
197				r#"{"jsonrpc":"2.0", "method":"subtract", "params":[42, 23], "id":1}"#,
198				Id::Number(1),
199				Some(params),
200				method,
201			),
202			// Without params field
203			(r#"{"jsonrpc":"2.0", "method":"subtract", "id":null}"#, Id::Null, None, method),
204			// Escaped method name.
205			(r#"{"jsonrpc":"2.0", "method":"\"m", "id":null}"#, Id::Null, None, "\"m"),
206		];
207
208		for (ser, id, params, method) in test_vector.into_iter() {
209			let request = serde_json::from_str(ser).unwrap();
210			assert_request(request, id, method, params);
211		}
212	}
213
214	#[test]
215	fn deserialize_call_escaped_method_name() {
216		let ser = r#"{"jsonrpc":"2.0","id":1,"method":"\"m\""}"#;
217		let req: Request = serde_json::from_str(ser).unwrap();
218		assert_request(req, Id::Number(1), "\"m\"", None);
219	}
220
221	#[test]
222	fn deserialize_valid_notif_works() {
223		let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[]}"#;
224		let dsr: Notification<&RawValue> = serde_json::from_str(ser).unwrap();
225		assert_eq!(dsr.method, "say_hello");
226		assert_eq!(dsr.jsonrpc, TwoPointZero);
227	}
228
229	#[test]
230	fn deserialize_valid_notif_escaped_method() {
231		let ser = r#"{"jsonrpc":"2.0","method":"\"m\"","params":[]}"#;
232		let dsr: Notification<&RawValue> = serde_json::from_str(ser).unwrap();
233		assert_eq!(dsr.method, "\"m\"");
234		assert_eq!(dsr.jsonrpc, TwoPointZero);
235	}
236
237	#[test]
238	fn deserialize_call_bad_id_should_fail() {
239		let ser = r#"{"jsonrpc":"2.0","method":"say_hello","params":[],"id":{}}"#;
240		assert!(serde_json::from_str::<Request>(ser).is_err());
241	}
242
243	#[test]
244	fn deserialize_invalid_request() {
245		let s = r#"{"id":120,"method":"my_method","params":["foo", "bar"],"extra_field":[]}"#;
246		let deserialized: InvalidRequest = serde_json::from_str(s).unwrap();
247		assert_eq!(deserialized, InvalidRequest { id: Id::Number(120) });
248	}
249
250	/// Checks that we can serialize the object with or without non-mandatory fields.
251	#[test]
252	fn serialize_call() {
253		let method = "subtract";
254		let id = Id::Number(1); // It's enough to check one variant, since the type itself also has tests.
255		let params = Some(RawValue::from_string("[42,23]".into()).unwrap());
256
257		let test_vector: &[(&'static str, Option<_>, Option<_>, &'static str)] = &[
258			// With all fields set.
259			(
260				r#"{"jsonrpc":"2.0","id":1,"method":"subtract","params":[42,23]}"#,
261				Some(id.clone()),
262				params.clone(),
263				method,
264			),
265			// Escaped method name.
266			(r#"{"jsonrpc":"2.0","id":1,"method":"\"m"}"#, Some(id.clone()), None, "\"m"),
267			// Without ID field.
268			(r#"{"jsonrpc":"2.0","id":null,"method":"subtract","params":[42,23]}"#, None, params, method),
269			// Without params field
270			(r#"{"jsonrpc":"2.0","id":1,"method":"subtract"}"#, Some(id), None, method),
271			// Without params and ID.
272			(r#"{"jsonrpc":"2.0","id":null,"method":"subtract"}"#, None, None, method),
273		];
274
275		for (ser, id, params, method) in test_vector.iter().cloned() {
276			let request = serde_json::to_string(&RequestSer {
277				jsonrpc: TwoPointZero,
278				method: method.into(),
279				id: id.unwrap_or(Id::Null),
280				params: params.map(Cow::Owned),
281			})
282			.unwrap();
283
284			assert_eq!(&request, ser);
285		}
286	}
287
288	#[test]
289	fn serialize_notif() {
290		let exp = r#"{"jsonrpc":"2.0","method":"say_hello","params":["hello"]}"#;
291		let params = Some(RawValue::from_string(r#"["hello"]"#.into()).unwrap());
292		let req = NotificationSer::owned("say_hello", params);
293		let ser = serde_json::to_string(&req).unwrap();
294		assert_eq!(exp, ser);
295	}
296
297	#[test]
298	fn serialize_notif_escaped_method_name() {
299		let exp = r#"{"jsonrpc":"2.0","method":"\"method\""}"#;
300		let req = NotificationSer::owned("\"method\"", None);
301		let ser = serde_json::to_string(&req).unwrap();
302		assert_eq!(exp, ser);
303	}
304}