alux_jsonrpc/algebra.rs
1use alux_ext::{ApplyAlg, ext};
2use trait_set::trait_set;
3
4/// Describes construction and composition of JSON-RPC method collections.
5pub trait JsonRpcAlg {
6 /// The interpreter's homogeneous method collection.
7 type Methods;
8
9 /// Returns the empty JSON-RPC method collection.
10 fn jsonrpc_empty(&self) -> Self::Methods;
11
12 /// Combines two JSON-RPC method collections.
13 fn jsonrpc_merge(&self, left: Self::Methods, right: Self::Methods) -> Self::Methods;
14}
15
16/// Compiles a typed JSON-RPC method declaration supported by an interpreter.
17pub trait JsonRpcMethodAlg<Context, Args, Output> {
18 /// Registers a first-order handler decoded from positional parameters.
19 ///
20 /// The argument names are stated even though positional decoding reads by position, because they
21 /// are part of what the method promises: an interpretation that describes or generates a client
22 /// can name the parameters a caller passes, whichever way the wire carries them.
23 fn finish_jsonrpc_positional_method<Handler>(
24 &self,
25 name: &'static str,
26 arg_names: &'static [&'static str],
27 handler: Handler,
28 ) -> Self::Methods
29 where
30 Self: JsonRpcAlg,
31 Handler: ApplyAlg<Context, Args, Output = Output> + Send + Sync + 'static;
32
33 /// Registers a first-order handler decoded from named parameters.
34 fn finish_jsonrpc_named_method<Handler>(
35 &self,
36 name: &'static str,
37 arg_names: &'static [&'static str],
38 handler: Handler,
39 ) -> Self::Methods
40 where
41 Self: JsonRpcAlg,
42 Handler: ApplyAlg<Context, Args, Output = Output> + Send + Sync + 'static;
43}
44
45/// Names the two halves of an outcome, so a bound can speak of a failure without spelling it.
46///
47/// A declaration states the conversion; this is what lets an interpretation name the value it answers
48/// with and the failure it reports, given only the operation's output.
49pub trait OutcomeAlg {
50 /// The value a successful outcome carries.
51 type Value;
52 /// The failure an unsuccessful outcome states.
53 type Error;
54
55 /// Reads the outcome as one or the other.
56 ///
57 /// # Errors
58 ///
59 /// Answers with `Err` when the outcome is the failure the operation stated.
60 fn outcome(self) -> Result<Self::Value, Self::Error>;
61}
62
63impl<Value, Error> OutcomeAlg for Result<Value, Error> {
64 type Value = Value;
65 type Error = Error;
66
67 fn outcome(self) -> Self {
68 self
69 }
70}
71
72/// States what a domain failure denotes on a JSON-RPC surface.
73///
74/// A domain says this once for its own error type: the code the JSON-RPC specification carries and
75/// the message the failure states. Nothing here names a transport library, so a specification can
76/// state what its failures mean without depending on whichever interpreter answers the call.
77pub trait RpcErrorAlg {
78 /// The code the JSON-RPC specification carries for this failure.
79 fn rpc_code(&self) -> i32;
80
81 /// The message this failure states.
82 fn rpc_message(&self) -> String;
83}
84
85/// An error that cannot be constructed converts to nothing, so a method carrying one answers only
86/// with its value. This is how a method keeps the value path inside an ext that converts every error.
87impl RpcErrorAlg for core::convert::Infallible {
88 fn rpc_code(&self) -> i32 {
89 match *self {}
90 }
91
92 fn rpc_message(&self) -> String {
93 match *self {}
94 }
95}
96
97/// Compiles a typed JSON-RPC method whose error answers as a protocol error.
98///
99/// A method registered here answers with its value or with a JSON-RPC error, so a domain that states
100/// failure in its own vocabulary reaches a caller as a failed call rather than as a successful one
101/// carrying an error-shaped value. [`OutcomeAlg`] names the two halves of the output and
102/// [`RpcErrorAlg`] says what the failing half denotes.
103pub trait JsonRpcFallibleAlg<Context, Args, Output> {
104 /// Registers a fallible handler decoded from positional parameters.
105 ///
106 /// The argument names are stated for the same reason as on [`JsonRpcMethodAlg`].
107 fn finish_jsonrpc_positional_fallible<Handler>(
108 &self,
109 name: &'static str,
110 arg_names: &'static [&'static str],
111 handler: Handler,
112 ) -> Self::Methods
113 where
114 Self: JsonRpcAlg,
115 Handler: ApplyAlg<Context, Args, Output = Output> + Send + Sync + 'static;
116
117 /// Registers a fallible handler decoded from named parameters.
118 fn finish_jsonrpc_named_fallible<Handler>(
119 &self,
120 name: &'static str,
121 arg_names: &'static [&'static str],
122 handler: Handler,
123 ) -> Self::Methods
124 where
125 Self: JsonRpcAlg,
126 Handler: ApplyAlg<Context, Args, Output = Output> + Send + Sync + 'static;
127}
128
129/// Interprets a named, defunctionalized JSON-RPC program with `Compiler`.
130pub trait JsonRpcProgramAlg<Compiler> {
131 /// The method collection produced by `Compiler`.
132 type Methods;
133
134 /// Compiles the program through the supplied interpreter.
135 fn compile_jsonrpc(self, compiler: &Compiler) -> Self::Methods;
136}
137
138trait_set! {
139 /// Combines the capabilities required to interpret JSON-RPC programs.
140 ///
141 /// Method registration is stated per operation signature, so composing a program surface needs
142 /// only the empty collection and its merge.
143 pub trait JsonRpcApiAlg = JsonRpcAlg;
144}
145
146/// Compiles defunctionalized JSON-RPC programs with an interpreter.
147#[ext(name = JsonRpcProgramExt)]
148pub impl<This> This {
149 /// Compiles a named JSON-RPC program with this interpreter.
150 fn compile_jsonrpc<Program>(&self, program: Program) -> Program::Methods
151 where
152 Program: JsonRpcProgramAlg<This>,
153 {
154 program.compile_jsonrpc(self)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::RpcErrorAlg;
161 use core::convert::Infallible;
162
163 #[test]
164 fn an_error_that_cannot_be_constructed_states_the_conversion() {
165 fn converts<Error: RpcErrorAlg>() {}
166
167 // This is what keeps a total method on the value path inside an ext that converts.
168 converts::<Infallible>();
169 }
170}