mod params;
#[cfg(feature = "typed")]
use std::sync::Arc;
use std::{any::TypeId, collections::HashMap};
use maf_schemas::packet::{TypedRpcRequestPacket, TypedRpcResponsePacket};
pub use params::Params;
use params::ParamsError;
use crate::{
callable::{BoxedCallable, CallableFetch},
platform::SendError,
App, LocalStateError, User,
};
pub struct RpcFunction {
pub(crate) method: String,
pub(crate) type_id: TypeId,
pub(crate) handler: BoxedCallable<RpcRequestContext, TypedRpcResponsePacket, RpcError>,
#[cfg(feature = "typed")]
pub(crate) desc: Arc<
dyn Fn(&mut schemars::SchemaGenerator) -> crate::typed::RpcDesc + Send + Sync + 'static,
>,
}
impl std::fmt::Debug for RpcFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RpcFunction")
.field("method", &self.method)
.field("type_id", &self.type_id)
.finish()
}
}
#[derive(Debug)]
pub struct RawRpcRequest {
pub id: u32,
pub user: User,
pub data: Option<RpcRequestData>,
}
#[derive(Debug)]
pub enum RpcRequestData {
Typed(serde_json::Value),
}
pub struct RpcRequestContext {
pub app: App,
pub request: RawRpcRequest,
}
pub struct RpcRequestInit {
pub method: String,
}
#[derive(Debug, Default)]
pub struct RpcStore {
pub(crate) inner: HashMap<String, RpcFunction>,
}
#[derive(Debug, thiserror::Error)]
pub enum RpcError {
#[error("rpc method `{0}` not found")]
MethodNotFound(String),
#[error("rpc function in `{0}` error: {1}")]
FunctionError(String, anyhow::Error),
#[error("rpc response serialization error: {0}")]
ResponseSerializationError(#[from] serde_json::Error),
#[error("rpc response error: {0}")]
ResponseError(#[from] SendError),
#[error("rpc params in `{0}` error: {1}")]
ParamsError(String, ParamsError),
#[error("other error: {0}")]
Other(#[from] anyhow::Error),
#[error("state error: {0}")]
State(#[from] LocalStateError),
#[error("infalliable error: {0}")]
Infalliable(#[from] std::convert::Infallible),
}
impl RpcStore {
pub fn add_rpc_function(&mut self, rpc_function: RpcFunction) {
self.inner.insert(rpc_function.method.clone(), rpc_function);
}
pub async fn handle_typed_rpc_request(
&self,
app: App,
user: &User,
packet: TypedRpcRequestPacket,
) -> Result<TypedRpcResponsePacket, RpcError> {
let method = packet.method;
let rpc_function = self
.inner
.get(&method)
.ok_or_else(|| RpcError::MethodNotFound(method))?;
let request = RawRpcRequest {
id: packet.id,
user: user.clone(),
data: Some(RpcRequestData::Typed(packet.params)),
};
let res = (rpc_function.handler)(RpcRequestContext { app, request });
tokio::pin!(res);
res.await
}
}
impl CallableFetch<App> for RpcRequestContext {
#[inline]
fn fetch(&self) -> App {
self.app.clone()
}
}
impl CallableFetch<User> for RpcRequestContext {
#[inline]
fn fetch(&self) -> User {
self.request.user.clone()
}
}
#[cfg(test)]
mod tests {
use crate::{callable::CallableParam, Store, StoreData};
use super::*;
#[test]
fn type_checks() {
const fn check_rpc_parameter<T: CallableParam<RpcRequestContext, RpcRequestInit>>() {}
check_rpc_parameter::<Params<i32>>();
check_rpc_parameter::<Params<(i32, String)>>();
struct T {}
impl StoreData for T {
type Select<'this> = ();
fn init() -> Self {
T {}
}
fn select(&self, _user: &User) -> Self::Select<'_> {
()
}
}
check_rpc_parameter::<Store<T>>();
}
}