pub struct WshRpcEngine {
inner: Mutex<EngineInner>,
output_tx: UnboundedSender<RpcMessage>,
}Expand description
Core RPC engine: handles incoming RPC requests, dispatches to registered command handlers, and manages request/response lifecycle.
Port of Go’s WshRpc from pkg/wshutil/wshrpc.go.
Fields§
§inner: Mutex<EngineInner>§output_tx: UnboundedSender<RpcMessage>Implementations§
Source§impl WshRpcEngine
impl WshRpcEngine
Sourcefn lock_inner(&self) -> MutexGuard<'_, EngineInner>
fn lock_inner(&self) -> MutexGuard<'_, EngineInner>
Lock inner, recovering from mutex poison instead of propagating
the panic. A poisoned mutex means a previous handler panicked while
holding the lock — the data is still consistent for our purposes
(inserts/removes on HashMaps), so recovery is safe here.
Sourcepub fn new() -> (Arc<Self>, UnboundedReceiver<RpcMessage>)
pub fn new() -> (Arc<Self>, UnboundedReceiver<RpcMessage>)
Create a new RPC engine. Returns the engine and a receiver for outgoing messages.
Sourcefn install_call_handler(
&self,
command: &str,
handler: Box<dyn Fn(Value, RpcContext) -> Pin<Box<dyn Future<Output = Result<Option<Value>, String>> + Send>> + Send + Sync>,
)
fn install_call_handler( &self, command: &str, handler: Box<dyn Fn(Value, RpcContext) -> Pin<Box<dyn Future<Output = Result<Option<Value>, String>> + Send>> + Send + Sync>, )
Install a call handler without touching the schema.
The one path that must not evict: Self::register_typed records a
binding and then installs its wrapper, so going through the public
Self::register_handler would delete the entry it had just written.
Sourcepub fn register_handler(
&self,
command: &str,
handler: Box<dyn Fn(Value, RpcContext) -> Pin<Box<dyn Future<Output = Result<Option<Value>, String>> + Send>> + Send + Sync>,
)
pub fn register_handler( &self, command: &str, handler: Box<dyn Fn(Value, RpcContext) -> Pin<Box<dyn Future<Output = Result<Option<Value>, String>> + Send>> + Send + Sync>, )
Register a call handler (single request → single response).
Drops any typed binding previously recorded for command: the last
registration wins for the handler map, so it must win for the schema
too, or schema_json() would advertise a request/response pair the
live handler no longer implements.
Sourcepub fn register_typed<Req, Resp, F, Fut>(
&self,
command: &'static str,
handler: F,
)
pub fn register_typed<Req, Resp, F, Fut>( &self, command: &'static str, handler: F, )
Register a call handler from typed request/response values, and
record the pair in this engine’s RpcSchema.
Behaviourally identical to Self::register_handler: the closure is
wrapped in exactly the deserialize → run → serialize steps every
hand-written handler already performs, so migrating one is not
supposed to change what goes over the wire. What it adds is that the
Req/Resp pairing becomes data the process can dump, which is
the prerequisite for generating the frontend’s bindings instead of
hand-maintaining them — see schema.rs and
docs/specs/SPEC_RPC_BINDINGS_CODEGEN_2026_09_07.md.
Two deliberate differences from the untyped path, both matching what the existing handlers do anyway:
- Deserialization failure is reported as
Err, with the command name and serde’s message — the sameformat!("{cmd}: {e}")shape the hand-written handlers use. - A response is always sent (
Ok(Some(..))). Handlers that must answer with nothing at all keep usingregister_handler;Resp = ()serializes tonull, which is a response, not silence.
Serialization failure is also an Err rather than a panic: a
response type whose Serialize fails is a bug, but it is not worth
taking the connection down for.
Both types must derive ts_rs::TS: the schema records each as
TypeScript, which is what the generated frontend stubs
(frontend/app/store/rpc-api/generated.ts) are built from. A typed
command whose types have no TypeScript would be one the frontend has
to describe by hand again.
Sourcepub fn register_stream_handler(
&self,
command: &str,
handler: Box<dyn Fn(Value, RpcContext) -> Pin<Box<dyn Future<Output = Result<Receiver<Result<Option<Value>, String>>, String>> + Send>> + Send + Sync>,
)
pub fn register_stream_handler( &self, command: &str, handler: Box<dyn Fn(Value, RpcContext) -> Pin<Box<dyn Future<Output = Result<Receiver<Result<Option<Value>, String>>, String>> + Send>> + Send + Sync>, )
Register a streaming handler (single request → stream of responses).
Sourcepub fn set_auth_token(&self, token: &str)
pub fn set_auth_token(&self, token: &str)
Set the authentication token.
Sourcepub fn get_auth_token(&self) -> String
pub fn get_auth_token(&self) -> String
Get the authentication token.
Sourcepub fn set_rpc_context(&self, ctx: RpcContext)
pub fn set_rpc_context(&self, ctx: RpcContext)
Set the RPC context.
Sourcepub fn handle_message(self: &Arc<Self>, msg: RpcMessage)
pub fn handle_message(self: &Arc<Self>, msg: RpcMessage)
Process an incoming message (from the transport layer).
Sourcepub async fn send_command(
self: &Arc<Self>,
command: &str,
data: Value,
opts: &RpcOpts,
) -> Result<Value, String>
pub async fn send_command( self: &Arc<Self>, command: &str, data: Value, opts: &RpcOpts, ) -> Result<Value, String>
Send an RPC command and wait for a single response.
Sourcepub fn send_request(
self: &Arc<Self>,
command: &str,
data: Value,
opts: &RpcOpts,
) -> Result<RpcRequestHandler, String>
pub fn send_request( self: &Arc<Self>, command: &str, data: Value, opts: &RpcOpts, ) -> Result<RpcRequestHandler, String>
Send an RPC command and get a request handler for streaming responses.
Sourcepub fn send_command_no_response(&self, command: &str, data: Value, route: &str)
pub fn send_command_no_response(&self, command: &str, data: Value, route: &str)
Send a fire-and-forget command (no response expected).
fn send_output(&self, msg: RpcMessage)
async fn handle_request(self: Arc<Self>, msg: RpcMessage)
fn handle_response(&self, msg: RpcMessage)
fn handle_cancel_request(&self, req_id: &str)
fn cleanup_handler(&self, req_id: &str)
Auto Trait Implementations§
impl !Freeze for WshRpcEngine
impl RefUnwindSafe for WshRpcEngine
impl Send for WshRpcEngine
impl Sync for WshRpcEngine
impl Unpin for WshRpcEngine
impl UnsafeUnpin for WshRpcEngine
impl UnwindSafe for WshRpcEngine
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more