use alloc::{string::String, vec, vec::Vec};
use serde::Serialize;
use serde_json::{Result, Value};
#[derive(Debug)]
pub struct RpcParams(ParamsBuilder);
impl RpcParams {
pub fn new() -> Self {
Self::default()
}
pub fn insert<P: Serialize>(&mut self, value: P) -> Result<()> {
self.0.insert(value)
}
pub fn insert_with_allocation<P: Serialize>(&mut self, value: P) -> Result<()> {
self.0.insert_with_allocation(value)
}
pub fn build(self) -> Option<String> {
self.0.build()
}
pub fn to_json_value(self) -> Result<Value> {
let params = match self.build() {
Some(string) => serde_json::from_str(&string)?,
None => serde_json::json!(vec![Value::Null]),
};
Ok(params)
}
}
impl Default for RpcParams {
fn default() -> Self {
Self(ParamsBuilder::positional())
}
}
const PARAM_BYTES_CAPACITY: usize = 128;
#[derive(Debug)]
pub(crate) struct ParamsBuilder {
bytes: Vec<u8>,
start: char,
end: char,
}
impl ParamsBuilder {
fn new(start: char, end: char) -> Self {
ParamsBuilder { bytes: Vec::new(), start, end }
}
pub(crate) fn positional() -> Self {
Self::new('[', ']')
}
fn maybe_initialize(&mut self) {
if self.bytes.is_empty() {
self.bytes.reserve(PARAM_BYTES_CAPACITY);
self.bytes.push(self.start as u8);
}
}
pub(crate) fn build(mut self) -> Option<String> {
if self.bytes.is_empty() {
return None
}
let idx = self.bytes.len() - 1;
if self.bytes[idx] == b',' {
self.bytes[idx] = self.end as u8;
} else {
self.bytes.push(self.end as u8);
}
Some(unsafe { String::from_utf8_unchecked(self.bytes) })
}
#[cfg(feature = "std")]
pub(crate) fn insert<P: Serialize>(&mut self, value: P) -> Result<()> {
self.maybe_initialize();
serde_json::to_writer(&mut self.bytes, &value)?;
self.bytes.push(b',');
Ok(())
}
#[cfg(not(feature = "std"))]
pub(crate) fn insert<P: Serialize>(&mut self, value: P) -> Result<()> {
self.insert_with_allocation(value)
}
pub(crate) fn insert_with_allocation<P: Serialize>(&mut self, value: P) -> Result<()> {
self.maybe_initialize();
let mut serialized_vec = serde_json::to_vec(&value)?;
self.bytes.append(&mut serialized_vec);
self.bytes.push(b',');
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_params_returns_none() {
let params = RpcParams::new();
let built_params = params.build();
assert!(built_params.is_none());
}
#[test]
fn insert_single_param_works() {
let mut params = RpcParams::new();
params.insert(Some(0)).unwrap();
let built_params = params.build().unwrap();
assert_eq!(built_params, "[0]".to_string());
}
#[test]
fn insert_multiple_params_works() {
let mut params = RpcParams::new();
params.insert(Some(0)).unwrap();
params.insert(0).unwrap();
let built_params = params.build().unwrap();
assert_eq!(built_params, "[0,0]".to_string());
}
#[test]
fn insert_with_allocation_multiple_params_works() {
let mut params = RpcParams::new();
params.insert_with_allocation(Some(0)).unwrap();
params.insert_with_allocation(0).unwrap();
let built_params = params.build().unwrap();
assert_eq!(built_params, "[0,0]".to_string());
}
}