ac_primitives/
rpc_params.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//! RPC parameters, originally belonging to jsonrpsee:
28//! https://github.com/paritytech/jsonrpsee
29//! It is copied & pasted here to avoid std dependencies.
30
31use alloc::{string::String, vec, vec::Vec};
32use serde::Serialize;
33use serde_json::{Result, Value};
34
35#[derive(Debug)]
36pub struct RpcParams(ParamsBuilder);
37
38impl RpcParams {
39	/// Construct a new [`RpcParams`].
40	pub fn new() -> Self {
41		Self::default()
42	}
43
44	/// Insert a plain value into the builder.
45	pub fn insert<P: Serialize>(&mut self, value: P) -> Result<()> {
46		self.0.insert(value)
47	}
48
49	/// Insert a plain value into the builder.
50	// Same functionality as `insert` but with the drawback of an extra heap allocation.
51	// But it is available in no_std.
52	pub fn insert_with_allocation<P: Serialize>(&mut self, value: P) -> Result<()> {
53		self.0.insert_with_allocation(value)
54	}
55
56	/// Finish the building process and return a JSON compatible string.
57	pub fn build(self) -> Option<String> {
58		self.0.build()
59	}
60
61	pub fn to_json_value(self) -> Result<Value> {
62		let params = match self.build() {
63			Some(string) => serde_json::from_str(&string)?,
64			None => serde_json::json!(vec![Value::Null]),
65		};
66		Ok(params)
67	}
68}
69
70impl Default for RpcParams {
71	fn default() -> Self {
72		Self(ParamsBuilder::positional())
73	}
74}
75/// Initial number of bytes for a parameter length.
76const PARAM_BYTES_CAPACITY: usize = 128;
77
78/// Generic parameter builder that serializes parameters to bytes.
79/// This produces a JSON compatible String.
80///
81/// The implementation relies on `Vec<u8>` to hold the serialized
82/// parameters in memory for the following reasons:
83///   1. Other serialization methods than `serde_json::to_writer` would internally
84///      have an extra heap allocation for temporarily holding the value in memory.
85///   2. `io::Write` is not implemented for `String` required for serialization.
86#[derive(Debug)]
87pub(crate) struct ParamsBuilder {
88	bytes: Vec<u8>,
89	start: char,
90	end: char,
91}
92
93impl ParamsBuilder {
94	/// Construct a new [`ParamsBuilder`] with custom start and end tokens.
95	/// The inserted values are wrapped by the _start_ and _end_ tokens.
96	fn new(start: char, end: char) -> Self {
97		ParamsBuilder { bytes: Vec::new(), start, end }
98	}
99
100	/// Construct a new [`ParamsBuilder`] for positional parameters equivalent to a JSON array object.
101	pub(crate) fn positional() -> Self {
102		Self::new('[', ']')
103	}
104
105	/// Initialize the internal vector if it is empty:
106	///  - allocate [`PARAM_BYTES_CAPACITY`] to avoid resizing
107	///  - add the `start` character.
108	///
109	/// # Note
110	///
111	/// Initialization is needed prior to inserting elements.
112	fn maybe_initialize(&mut self) {
113		if self.bytes.is_empty() {
114			self.bytes.reserve(PARAM_BYTES_CAPACITY);
115			self.bytes.push(self.start as u8);
116		}
117	}
118
119	/// Finish the building process and return a JSON compatible string.
120	pub(crate) fn build(mut self) -> Option<String> {
121		if self.bytes.is_empty() {
122			return None
123		}
124
125		let idx = self.bytes.len() - 1;
126		if self.bytes[idx] == b',' {
127			self.bytes[idx] = self.end as u8;
128		} else {
129			self.bytes.push(self.end as u8);
130		}
131
132		// Safety: This is safe because JSON does not emit invalid UTF-8.
133		Some(unsafe { String::from_utf8_unchecked(self.bytes) })
134	}
135
136	/// Insert a plain value into the builder without heap allocation.
137	#[cfg(feature = "std")]
138	pub(crate) fn insert<P: Serialize>(&mut self, value: P) -> Result<()> {
139		self.maybe_initialize();
140
141		serde_json::to_writer(&mut self.bytes, &value)?;
142		self.bytes.push(b',');
143
144		Ok(())
145	}
146
147	/// Insert a plain value into the builder with heap allocation. If available,
148	/// use the more efficient std version.
149	#[cfg(not(feature = "std"))]
150	pub(crate) fn insert<P: Serialize>(&mut self, value: P) -> Result<()> {
151		self.insert_with_allocation(value)
152	}
153
154	/// Insert a plain value into the builder with heap allocation. For better performance,
155	/// use the std version, if possible.
156	pub(crate) fn insert_with_allocation<P: Serialize>(&mut self, value: P) -> Result<()> {
157		self.maybe_initialize();
158
159		let mut serialized_vec = serde_json::to_vec(&value)?;
160		self.bytes.append(&mut serialized_vec);
161		self.bytes.push(b',');
162
163		Ok(())
164	}
165}
166
167#[cfg(test)]
168mod tests {
169	use super::*;
170
171	#[test]
172	fn default_params_returns_none() {
173		let params = RpcParams::new();
174		let built_params = params.build();
175		assert!(built_params.is_none());
176	}
177
178	#[test]
179	fn insert_single_param_works() {
180		let mut params = RpcParams::new();
181		params.insert(Some(0)).unwrap();
182		let built_params = params.build().unwrap();
183		assert_eq!(built_params, "[0]".to_string());
184	}
185
186	#[test]
187	fn insert_multiple_params_works() {
188		let mut params = RpcParams::new();
189		params.insert(Some(0)).unwrap();
190		params.insert(0).unwrap();
191		let built_params = params.build().unwrap();
192		assert_eq!(built_params, "[0,0]".to_string());
193	}
194
195	#[test]
196	fn insert_with_allocation_multiple_params_works() {
197		let mut params = RpcParams::new();
198		params.insert_with_allocation(Some(0)).unwrap();
199		params.insert_with_allocation(0).unwrap();
200		let built_params = params.build().unwrap();
201		assert_eq!(built_params, "[0,0]".to_string());
202	}
203}