1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use crate::handler::RpcHandlerWrapperTrait;
use crate::{Error, Result, RpcHandler, RpcResources};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt;

/// method, which calls the appropriate handler matching the method_name.
///
/// RpcRouter can be extended with other RpcRouters for composability.
pub struct RpcRouter {
	route_by_name: HashMap<&'static str, Box<dyn RpcHandlerWrapperTrait>>,
}

impl fmt::Debug for RpcRouter {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		f.debug_struct("RpcRouter")
			.field("route_by_name", &self.route_by_name.keys())
			.finish()
	}
}

impl RpcRouter {
	#[allow(clippy::new_without_default)] // Persosnal preference (for this case)
	pub fn new() -> Self {
		Self {
			route_by_name: HashMap::new(),
		}
	}

	/// Add a dyn_handler to the router.
	///
	/// ```
	/// RpcRouter::new().add_dyn("method_name", my_handler_fn.into_dyn());
	/// ```
	///
	/// Note: This is the preferred way to add handlers to the router, as it
	///       avoids monomorphization of the add function.
	///       The RpcRouter also has a `.add()` as a convenience function to just pass the function.
	///       See `RpcRouter::add` for more details.
	pub fn add_dyn(mut self, name: &'static str, dyn_handler: Box<dyn RpcHandlerWrapperTrait>) -> Self {
		self.route_by_name.insert(name, dyn_handler);
		self
	}

	/// Add an handler function to the router.
	///
	/// ```
	/// RpcRouter::new().add("method_name", my_handler_fn);
	/// ```
	///
	/// Note: This is a convenient add function variant with generics,
	///       and there will be monomorphed versions of this function
	///       for each type passed. Use `RpcRouter::add_dyn` to avoid this.
	pub fn add<F, T, P, R>(self, name: &'static str, handler: F) -> Self
	where
		F: RpcHandler<T, P, R> + Clone + Send + Sync + 'static,
		T: Send + Sync + 'static,
		P: Send + Sync + 'static,
		R: Send + Sync + 'static,
	{
		self.add_dyn(name, handler.into_dyn())
	}

	pub fn extend(mut self, other_router: RpcRouter) -> Self {
		self.route_by_name.extend(other_router.route_by_name);
		self
	}

	pub async fn call(&self, method: &str, rpc_resources: RpcResources, params: Option<Value>) -> Result<Value> {
		if let Some(route) = self.route_by_name.get(method) {
			route.call(rpc_resources, params).await
		} else {
			Err(Error::RpcMethodUnknown(method.to_string()))
		}
	}
}

/// A simple macro to create a new RpcRouter
/// and add each rpc handler-compatible function along with their corresponding names.
///
/// e.g.,
///
/// ```
/// rpc_router!(
///   create_project,
///   list_projects,
///   update_project,
///   delete_project
/// );
/// ```
/// Is equivalent to:
/// ```
/// RpcRouter::new()
///     .add_dyn("create_project", create_project.into_box())
///     .add_dyn("list_projects", list_projects.into_box())
///     .add_dyn("update_project", update_project.into_box())
///     .add_dyn("delete_project", delete_project.into_box())
/// ```
#[macro_export]
macro_rules! rpc_router {
    ($($fn_name:ident),+ $(,)?) => {
        {
					use rpc_router::{RpcHandler, RpcRouter};
					let mut router = RpcRouter::new();
					$(
							router = router.add_dyn(stringify!($fn_name), $fn_name.into_dyn());
					)+
					router
        }
    };
}