Skip to main content

alux_jsonrpc_direct/
dispatch.rs

1use crate::error::RpcError;
2use crate::table::MethodTable;
3use serde::Serialize;
4use serde_json::Value;
5use std::sync::Arc;
6
7/// What one call answers with, in the member order the specification presents.
8#[derive(Debug, Serialize)]
9struct Response {
10    jsonrpc: &'static str,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    result: Option<Value>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    error: Option<RpcError>,
15    id: Value,
16}
17
18impl Response {
19    /// Reads one call's outcome as the response it denotes.
20    fn of(id: Value, outcome: Result<Value, RpcError>) -> Self {
21        match outcome {
22            Ok(result) => Self { jsonrpc: "2.0", result: Some(result), error: None, id },
23            Err(error) => Self { jsonrpc: "2.0", result: None, error: Some(error), id },
24        }
25    }
26}
27
28/// What one request document answers with: a single response, or one per non-notification call.
29#[derive(Debug, Serialize)]
30#[serde(untagged)]
31enum Answered {
32    One(Response),
33    Many(Vec<Response>),
34}
35
36/// Reads the request members a call supplies, or states why the document is not a request.
37fn members(call: Value) -> Result<serde_json::Map<String, Value>, RpcError> {
38    match call {
39        Value::Object(members) => Ok(members),
40        _ => Err(RpcError::invalid_request()),
41    }
42}
43
44/// States whether a value can identify a call, which the specification limits to a string, number,
45/// or null.
46fn is_identifier(id: &Value) -> bool {
47    matches!(id, Value::String(_) | Value::Number(_) | Value::Null)
48}
49
50impl MethodTable {
51    /// Answers one JSON-RPC request document.
52    ///
53    /// Answers with nothing when the document asks for nothing: a notification, or a batch of them.
54    pub async fn dispatch(&self, request: &str) -> Option<String> {
55        let answered = match serde_json::from_str::<Value>(request) {
56            Ok(document) => self.answer(document).await?,
57            Err(_) => Answered::One(Response::of(Value::Null, Err(RpcError::parse_error()))),
58        };
59
60        Some(render(&answered))
61    }
62
63    /// Answers one parsed request document, distinguishing a batch from a single call.
64    async fn answer(&self, document: Value) -> Option<Answered> {
65        let Value::Array(calls) = document else {
66            return self.call(document).await.map(Answered::One);
67        };
68        if calls.is_empty() {
69            return Some(Answered::One(Response::of(Value::Null, Err(RpcError::invalid_request()))));
70        }
71        let mut answers = Vec::new();
72        for call in calls {
73            if let Some(answer) = self.call(call).await {
74                answers.push(answer);
75            }
76        }
77
78        (!answers.is_empty()).then_some(Answered::Many(answers))
79    }
80
81    /// Answers one call, or nothing when the call is a notification.
82    async fn call(&self, call: Value) -> Option<Response> {
83        let (id, outcome) = self.outcome(call).await;
84
85        id.map(|id| Response::of(id, outcome))
86    }
87
88    /// Reads what one call asks for, answering with the identifier to respond to and the outcome.
89    ///
90    /// An absent identifier means a notification, which is answered by saying nothing at all — even
91    /// when the call itself is malformed.
92    async fn outcome(&self, call: Value) -> (Option<Value>, Result<Value, RpcError>) {
93        let mut members = match members(call) {
94            Ok(members) => members,
95            Err(error) => return (Some(Value::Null), Err(error)),
96        };
97        // An absent identifier is what makes a call a notification, so it decides whether to answer
98        // at all; every later step reports through it.
99        let respond_to = members.remove("id");
100        if respond_to.as_ref().is_some_and(|id| !is_identifier(id)) {
101            return (Some(Value::Null), Err(RpcError::invalid_request()));
102        }
103        if members.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
104            return (respond_to, Err(RpcError::invalid_request()));
105        }
106        let Some(Value::String(method)) = members.remove("method") else {
107            return (respond_to, Err(RpcError::invalid_request()));
108        };
109        let params = members.remove("params");
110        if params.as_ref().is_some_and(|params| !(params.is_array() || params.is_object() || params.is_null())) {
111            return (respond_to, Err(RpcError::invalid_request()));
112        }
113        let Some(method) = self.get(method.as_str()).map(Arc::clone) else {
114            return (respond_to, Err(RpcError::method_not_found(&method)));
115        };
116
117        (respond_to, method(params).await)
118    }
119}
120
121/// Renders an answer, stating an internal error if it somehow cannot be serialized.
122fn render(answered: &Answered) -> String {
123    serde_json::to_string(answered).unwrap_or_else(|_| {
124        r#"{"jsonrpc":"2.0","error":{"code":-32603,"message":"the answer cannot be serialized"},"id":null}"#.to_owned()
125    })
126}