Skip to main content

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}