use crate::server::helpers::{send_error, send_response};
use futures_channel::mpsc;
use jsonrpsee_types::error::{CallError, Error};
use jsonrpsee_types::traits::RpcMethod;
use jsonrpsee_types::v2::error::{JsonRpcErrorCode, JsonRpcErrorObject, CALL_EXECUTION_FAILED_CODE};
use jsonrpsee_types::v2::params::{Id, JsonRpcNotificationParams, RpcParams, TwoPointZero};
use jsonrpsee_types::v2::response::JsonRpcSubscriptionResponse;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::Serialize;
use serde_json::value::to_raw_value;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
pub type Method = Box<dyn Send + Sync + Fn(Id, RpcParams, &MethodSink, ConnectionId) -> Result<(), Error>>;
pub type Methods = FxHashMap<&'static str, Method>;
pub type ConnectionId = usize;
pub type SubscriptionId = u64;
pub type MethodSink = mpsc::UnboundedSender<String>;
type Subscribers = Arc<Mutex<FxHashMap<(ConnectionId, SubscriptionId), MethodSink>>>;
#[derive(Default)]
pub struct RpcModule {
methods: Methods,
}
impl RpcModule {
pub fn new() -> Self {
RpcModule { methods: Methods::default() }
}
pub fn with_context<Context>(self, ctx: Context) -> RpcContextModule<Context> {
RpcContextModule { ctx: Arc::new(ctx), module: self }
}
fn verify_method_name(&mut self, name: &str) -> Result<(), Error> {
if self.methods.get(name).is_some() {
return Err(Error::MethodAlreadyRegistered(name.into()));
}
Ok(())
}
pub fn register_method<R, F>(&mut self, method_name: &'static str, callback: F) -> Result<(), Error>
where
R: Serialize,
F: RpcMethod<R, CallError>,
{
self.verify_method_name(method_name)?;
self.methods.insert(
method_name,
Box::new(move |id, params, tx, _| {
match callback(params) {
Ok(res) => send_response(id, tx, res),
Err(CallError::InvalidParams) => send_error(id, tx, JsonRpcErrorCode::InvalidParams.into()),
Err(CallError::Failed(err)) => {
log::error!("Call failed with: {}", err);
let err = JsonRpcErrorObject {
code: JsonRpcErrorCode::ServerError(CALL_EXECUTION_FAILED_CODE),
message: &err.to_string(),
data: None,
};
send_error(id, tx, err)
}
};
Ok(())
}),
);
Ok(())
}
pub fn register_subscription(
&mut self,
subscribe_method_name: &'static str,
unsubscribe_method_name: &'static str,
) -> Result<SubscriptionSink, Error> {
if subscribe_method_name == unsubscribe_method_name {
return Err(Error::SubscriptionNameConflict(subscribe_method_name.into()));
}
self.verify_method_name(subscribe_method_name)?;
self.verify_method_name(unsubscribe_method_name)?;
let subscribers = Arc::new(Mutex::new(FxHashMap::default()));
{
let subscribers = subscribers.clone();
self.methods.insert(
subscribe_method_name,
Box::new(move |id, _, tx, conn| {
let sub_id = {
const JS_NUM_MASK: SubscriptionId = !0 >> 11;
let sub_id = rand::random::<SubscriptionId>() & JS_NUM_MASK;
subscribers.lock().insert((conn, sub_id), tx.clone());
sub_id
};
send_response(id, tx, sub_id);
Ok(())
}),
);
}
{
let subscribers = subscribers.clone();
self.methods.insert(
unsubscribe_method_name,
Box::new(move |id, params, tx, conn| {
let sub_id = params.one()?;
subscribers.lock().remove(&(conn, sub_id));
send_response(id, tx, "Unsubscribed");
Ok(())
}),
);
}
Ok(SubscriptionSink { method: subscribe_method_name, subscribers })
}
pub fn into_methods(self) -> Methods {
self.methods
}
pub fn merge(&mut self, other: RpcModule) -> Result<(), Error> {
for name in other.methods.keys() {
self.verify_method_name(name)?;
}
for (name, callback) in other.methods {
self.methods.insert(name, callback);
}
Ok(())
}
}
pub struct RpcContextModule<Context> {
ctx: Arc<Context>,
module: RpcModule,
}
impl<Context> RpcContextModule<Context> {
pub fn new(ctx: Context) -> Self {
RpcContextModule { ctx: Arc::new(ctx), module: RpcModule::new() }
}
pub fn register_method<R, F>(&mut self, method_name: &'static str, callback: F) -> Result<(), Error>
where
Context: Send + Sync + 'static,
R: Serialize,
F: Fn(RpcParams, &Context) -> Result<R, CallError> + Send + Sync + 'static,
{
self.module.verify_method_name(method_name)?;
let ctx = self.ctx.clone();
self.module.methods.insert(
method_name,
Box::new(move |id, params, tx, _| {
match callback(params, &*ctx) {
Ok(res) => send_response(id, tx, res),
Err(CallError::InvalidParams) => send_error(id, tx, JsonRpcErrorCode::InvalidParams.into()),
Err(CallError::Failed(err)) => {
let err = JsonRpcErrorObject {
code: JsonRpcErrorCode::ServerError(CALL_EXECUTION_FAILED_CODE),
message: &err.to_string(),
data: None,
};
send_error(id, tx, err)
}
};
Ok(())
}),
);
Ok(())
}
pub fn into_module(self) -> RpcModule {
self.module
}
pub fn into_methods(self) -> Methods {
self.into_module().into_methods()
}
}
impl<Cx> Deref for RpcContextModule<Cx> {
type Target = RpcModule;
fn deref(&self) -> &Self::Target {
&self.module
}
}
impl<Cx> DerefMut for RpcContextModule<Cx> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.module
}
}
#[derive(Clone)]
pub struct SubscriptionSink {
method: &'static str,
subscribers: Subscribers,
}
impl SubscriptionSink {
pub fn send<T>(&mut self, result: &T) -> Result<(), Error>
where
T: Serialize,
{
let result = to_raw_value(result)?;
let mut errored = Vec::new();
let mut subs = self.subscribers.lock();
for ((conn_id, sub_id), sender) in subs.iter() {
let msg = serde_json::to_string(&JsonRpcSubscriptionResponse {
jsonrpc: TwoPointZero,
method: self.method,
params: JsonRpcNotificationParams { subscription: *sub_id, result: &*result },
})?;
if sender.unbounded_send(msg).is_err() {
errored.push((*conn_id, *sub_id));
}
}
for entry in errored {
subs.remove(&entry);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rpc_context_modules_can_merge_with_rpc_module() {
let cx = Vec::<u8>::new();
let mut cxmodule = RpcContextModule::new(cx);
cxmodule.register_method("bla with context", |_: RpcParams, _| Ok(())).unwrap();
let mut module = RpcModule::new();
module.register_method("bla", |_: RpcParams| Ok(())).unwrap();
cxmodule.merge(module).unwrap();
let mut cx_methods = cxmodule.into_methods().keys().cloned().collect::<Vec<&str>>();
cx_methods.sort();
assert_eq!(cx_methods, vec!["bla", "bla with context"]);
}
#[test]
fn rpc_context_modules_can_register_subscriptions() {
let cx = ();
let mut cxmodule = RpcContextModule::new(cx);
let _subscription = cxmodule.register_subscription("hi", "goodbye");
let methods = cxmodule.into_methods().keys().cloned().collect::<Vec<&str>>();
assert!(methods.contains(&"hi"));
assert!(methods.contains(&"goodbye"));
}
}