A super light JSON RPC 2.0 implementation.
WARNING: This is very early in development and is subject to significant change.
Creating a server
As of right now, seraphic only handles the creation of servers. Clients can be created any way you choose, so long as you dial the correct address and send messages compliant with the JSON RPC 2.0 specification.
RpcListeningThread
This is the main struct for handling all server operations, one can be created with RpcListeningThread::new. Requests can be polled from recv , and responses can be sent back through sender.
let server_thread = new?;
if let Some = server_thread.recv.recv.await
Important traits
Sending JSON through a server is easy enough, but what's really helpful about seraphic is the way it abstracts Request Methods, expected Responses, and errors. These are the traits used to facilitate this abstraction:
RpcNamespace- Facilitates the management of method namespaces.RpcRequest- Defines the namespace/method a request is associated with & facilitates serialization to/from thesocket::Requeststruct.RpcRequestWrapper- a wrapper struct meant to contain all requests your server acceptsRpcResponse- Simply a marker trait for marking a struct as what you expect to be returned from the successful processing of a request. The best thing about all these traits is that they each have a derive implementation for minimal boilerplate!RpcHandler- to be implemented on whatever you are using to process requests to return responses
Example
// This will define the namespaces "foo", "bar", and "baz"
// The rpc_request derive attribute *requires* you pass a namespace argument, which is formatted as "<Namespace Struct Name>:<variant>"
// The RpcRequest derive macro expects the struct it is derived on to end in the suffix 'Request', and for there to be another struct with the same prefix, but with 'Response' as the suffix.
// RpcRequest's Derive macro will expand to implement RpcResponse on it's associated response struct
// If you wish to use a struct by a different name for your expected response object, you can pass it in the rpc_request attribute.
With the above code, we have defined two request object, each have been mapped to their own methods:
SomeBarRequest- "bar_someBar"SomeBazRequest- "baz_someBaz"
Now we have defined namespacing for specific kinds of methods in our api! The next step is to create a RpcRequestWrapper so we can easily hande parsing all of our requests:
// As long as each variant in this enum implements RpcRequest, this derive macro should work
Now when we receive a request through an RpcListeningThread, we can coerce it to this wrapper struct and handle all possible requests:
if let Some = server_thread.recv.recv.await
RpcHandler
I have also created a trait called RpcHandler. It may add too much abstraction, so it may be removed in the future, but it compartmentalizes handling requests a little more.
pub type ProcessRequestResult = ;
Since socket::Response implements From<ProcessRequestResult> it makes managing returning error/successful responses a little easier. But this trait is not required to implement a JSON RPC api.