alux-jsonrpc
alux-jsonrpc describes typed JSON-RPC method programs independently of an RPC framework.
The crate is a specification. It carries no interpreter and depends only on
alux-ext: a JSON-RPC surface is described here as first-order syntax
plus the capability algebras an interpreter must witness. Domain specifications provide their own
capability traits and compose defunctionalized operations.
use ;
use ;
use Future;
/// A downstream specification owns its primitive domain meaning.
/// Derived operations become first-order values that preserve their argument names.
/// The method program is declared before any framework is chosen.
;
// The same program is constructible directly, without the convenience macro and without an
// interpreter, because a method program is an ordinary value.
let builder = JsonRpcProgramBuilder;
let program = builder
.methods
// The same method, declared without the convenience macro.
.method
.into_program;
let _merged = builder.methods.merge.into_program;
// Named decoding means these argument names, carried from the authored method.
assert_eq!;
Composing surfaces
The reason to keep a surface first-order is that programs compose before anything interprets them. Two crates that know nothing about each other can each declare part of a service, and a third can state the whole of it — no shared method table, no registry, no framework in the picture yet.
use ext;
use ;
use Future;
/// One surface fragment, offering one operation under both parameter modes.
/// Another fragment, declared independently — plausibly in another crate.
/// The whole service, as the union of both fragments.
Two things there are worth pausing on, because the authored text is not what runs:
- The declarations look like they return nothing. They do return something. The macro replaces the
written signature with
-> ServiceRpcProgram<Alg>and the written body withServiceRpcProgram::default(), so callingservice_rpchands back a zero-sized program value. Writing no return type is the convention: the type is generated, and naming it by hand would only repeat the macro. mergeis not receiving a method collection. The authored body is read as a description rather than executed, and a call to a sibling declaration is lifted into a nested program — the emitted body readsbuilder.merge(builder.program(builder.status_rpc::<Alg>())). That is what type-checks, and it is why one fragment composes with another without either knowing how the other was declared.
What that buys, and why it is not just tidiness:
- Fragments state their own dependencies. Each names the capability it uses and nothing more, and
service_rpcinherits exactly the union. - Merge is the whole composition. A JSON-RPC surface is a set of named methods, so it composes as a monoid — there is no prefix to nest under, because a method name has no parts. HTTP carries selectors and therefore nesting; the two program algebras differ because their surfaces genuinely differ.
- One operation can appear more than once.
status_adjustedis registered twice above, positionally and by name, which is a decision about the wire rather than about the domain. - Composition happens before interpretation.
service_rpcis a value; the interpreter that registers it never chooses a method name.
Positional parameters are the default. .named() decodes a JSON object using the argument names
retained by alux-ext.
alux-jsonrpc-jsonrpsee compiles the same program into
jsonrpsee Methods.