/ src / rpc / jsonrpc.rs
jsonrpc.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  //! JSON-RPC 2.0 object definitions
 20  use std::collections::HashMap;
 21  
 22  use rand::{rngs::OsRng, Rng};
 23  use tinyjson::JsonValue;
 24  
 25  use crate::{
 26      error::RpcError,
 27      system::{Publisher, PublisherPtr},
 28      Result,
 29  };
 30  
 31  /// JSON-RPC error codes.
 32  /// The error codes `[-32768, -32000]` are reserved for predefined errors.
 33  #[derive(Copy, Clone, Debug)]
 34  pub enum ErrorCode {
 35      /// Invalid JSON was received by the server.
 36      /// An error occurred on the server while parsing the JSON text.
 37      ParseError,
 38      /// The JSON sent is not a valid Request object.
 39      InvalidRequest,
 40      /// The method does not exist / is not available.
 41      MethodNotFound,
 42      /// Invalid method parameter(s).
 43      InvalidParams,
 44      /// Internal JSON-RPC error.
 45      InternalError,
 46      /// ID mismatch
 47      IdMismatch,
 48      /// Invalid/Unexpected reply
 49      InvalidReply,
 50      /// Reserved for implementation-defined server-errors.
 51      ServerError(i32),
 52  }
 53  
 54  impl ErrorCode {
 55      pub fn code(&self) -> i32 {
 56          match *self {
 57              Self::ParseError => -32700,
 58              Self::InvalidRequest => -32600,
 59              Self::MethodNotFound => -32601,
 60              Self::InvalidParams => -32602,
 61              Self::InternalError => -32603,
 62              Self::IdMismatch => -32360,
 63              Self::InvalidReply => -32361,
 64              Self::ServerError(c) => c,
 65          }
 66      }
 67  
 68      pub fn message(&self) -> String {
 69          match *self {
 70              Self::ParseError => "parse error".to_string(),
 71              Self::InvalidRequest => "invalid request".to_string(),
 72              Self::MethodNotFound => "method not found".to_string(),
 73              Self::InvalidParams => "invalid params".to_string(),
 74              Self::InternalError => "internal error".to_string(),
 75              Self::IdMismatch => "id mismatch".to_string(),
 76              Self::InvalidReply => "invalid reply".to_string(),
 77              Self::ServerError(_) => "server error".to_string(),
 78          }
 79      }
 80  
 81      pub fn desc(&self) -> JsonValue {
 82          JsonValue::String(self.message())
 83      }
 84  }
 85  
 86  // ANCHOR: jsonresult
 87  /// Wrapping enum around the available JSON-RPC object types
 88  #[derive(Clone, Debug)]
 89  pub enum JsonResult {
 90      Response(JsonResponse),
 91      Error(JsonError),
 92      Notification(JsonNotification),
 93      /// Subscriber is a special object that yields a channel
 94      Subscriber(JsonSubscriber),
 95      SubscriberWithReply(JsonSubscriber, JsonResponse),
 96      Request(JsonRequest),
 97  }
 98  
 99  impl JsonResult {
100      pub fn try_from_value(value: &JsonValue) -> Result<Self> {
101          if let Ok(response) = JsonResponse::try_from(value) {
102              return Ok(Self::Response(response))
103          }
104  
105          if let Ok(error) = JsonError::try_from(value) {
106              return Ok(Self::Error(error))
107          }
108  
109          if let Ok(notification) = JsonNotification::try_from(value) {
110              return Ok(Self::Notification(notification))
111          }
112  
113          Err(RpcError::InvalidJson("Invalid JSON Result".to_string()).into())
114      }
115  }
116  
117  impl From<JsonResponse> for JsonResult {
118      fn from(resp: JsonResponse) -> Self {
119          Self::Response(resp)
120      }
121  }
122  
123  impl From<JsonError> for JsonResult {
124      fn from(err: JsonError) -> Self {
125          Self::Error(err)
126      }
127  }
128  
129  impl From<JsonNotification> for JsonResult {
130      fn from(notif: JsonNotification) -> Self {
131          Self::Notification(notif)
132      }
133  }
134  
135  impl From<JsonSubscriber> for JsonResult {
136      fn from(sub: JsonSubscriber) -> Self {
137          Self::Subscriber(sub)
138      }
139  }
140  
141  impl From<(JsonSubscriber, JsonResponse)> for JsonResult {
142      fn from(tuple: (JsonSubscriber, JsonResponse)) -> Self {
143          Self::SubscriberWithReply(tuple.0, tuple.1)
144      }
145  }
146  
147  // ANCHOR: jsonrequest
148  /// A JSON-RPC request object
149  #[derive(Clone, Debug)]
150  pub struct JsonRequest {
151      /// JSON-RPC version
152      pub jsonrpc: &'static str,
153      /// Request ID
154      pub id: u16,
155      /// Request method
156      pub method: String,
157      /// Request parameters
158      pub params: JsonValue,
159  }
160  // ANCHOR_END: jsonrequest
161  
162  impl JsonRequest {
163      /// Create a new [`JsonRequest`] object with the given method and parameters.
164      /// The request ID is chosen randomly.
165      pub fn new(method: &str, params: JsonValue) -> Self {
166          assert!(params.is_object() || params.is_array());
167          Self { jsonrpc: "2.0", id: OsRng::gen(&mut OsRng), method: method.to_string(), params }
168      }
169  
170      /// Convert the object into a JSON string
171      pub fn stringify(&self) -> Result<String> {
172          let v: JsonValue = self.into();
173          Ok(v.stringify()?)
174      }
175  }
176  
177  impl From<&JsonRequest> for JsonValue {
178      fn from(req: &JsonRequest) -> JsonValue {
179          JsonValue::Object(HashMap::from([
180              ("jsonrpc".to_string(), JsonValue::String(req.jsonrpc.to_string())),
181              ("id".to_string(), JsonValue::Number(req.id.into())),
182              ("method".to_string(), JsonValue::String(req.method.clone())),
183              ("params".to_string(), req.params.clone()),
184          ]))
185      }
186  }
187  
188  impl TryFrom<&JsonValue> for JsonRequest {
189      type Error = RpcError;
190  
191      fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
192          if !value.is_object() {
193              return Err(RpcError::InvalidJson("JSON is not an Object".to_string()))
194          }
195  
196          let map: &HashMap<String, JsonValue> = value.get().unwrap();
197  
198          if !map.contains_key("jsonrpc") ||
199              !map["jsonrpc"].is_string() ||
200              map["jsonrpc"] != JsonValue::String("2.0".to_string())
201          {
202              return Err(RpcError::InvalidJson(
203                  "Request does not contain valid \"jsonrpc\" field".to_string(),
204              ))
205          }
206  
207          if !map.contains_key("id") || !map["id"].is_number() {
208              return Err(RpcError::InvalidJson(
209                  "Request does not contain valid \"id\" field".to_string(),
210              ))
211          }
212  
213          if !map.contains_key("method") || !map["method"].is_string() {
214              return Err(RpcError::InvalidJson(
215                  "Request does not contain valid \"method\" field".to_string(),
216              ))
217          }
218  
219          if !map.contains_key("params") {
220              return Err(RpcError::InvalidJson(
221                  "Request does not contain valid \"params\" field".to_string(),
222              ))
223          }
224  
225          if !map["params"].is_object() && !map["params"].is_array() {
226              return Err(RpcError::InvalidJson(
227                  "Request does not contain valid \"params\" field".to_string(),
228              ))
229          }
230  
231          Ok(Self {
232              jsonrpc: "2.0",
233              id: *map["id"].get::<f64>().unwrap() as u16,
234              method: map["method"].get::<String>().unwrap().clone(),
235              params: map["params"].clone(),
236          })
237      }
238  }
239  
240  /// A JSON-RPC notification object
241  #[derive(Clone, Debug)]
242  pub struct JsonNotification {
243      /// JSON-RPC version
244      pub jsonrpc: &'static str,
245      /// Notification method
246      pub method: String,
247      /// Notification parameters
248      pub params: JsonValue,
249  }
250  
251  impl JsonNotification {
252      /// Create a new [`JsonNotification`] object with the given method and parameters.
253      pub fn new(method: &str, params: JsonValue) -> Self {
254          assert!(params.is_object() || params.is_array());
255          Self { jsonrpc: "2.0", method: method.to_string(), params }
256      }
257  
258      /// Convert the object into a JSON string
259      pub fn stringify(&self) -> Result<String> {
260          let v: JsonValue = self.into();
261          Ok(v.stringify()?)
262      }
263  }
264  
265  impl From<&JsonNotification> for JsonValue {
266      fn from(notif: &JsonNotification) -> JsonValue {
267          JsonValue::Object(HashMap::from([
268              ("jsonrpc".to_string(), JsonValue::String(notif.jsonrpc.to_string())),
269              ("method".to_string(), JsonValue::String(notif.method.clone())),
270              ("params".to_string(), notif.params.clone()),
271          ]))
272      }
273  }
274  
275  impl TryFrom<&JsonValue> for JsonNotification {
276      type Error = RpcError;
277  
278      fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
279          if !value.is_object() {
280              return Err(RpcError::InvalidJson("JSON is not an Object".to_string()))
281          }
282  
283          let map: &HashMap<String, JsonValue> = value.get().unwrap();
284  
285          if !map.contains_key("jsonrpc") ||
286              !map["jsonrpc"].is_string() ||
287              map["jsonrpc"] != JsonValue::String("2.0".to_string())
288          {
289              return Err(RpcError::InvalidJson(
290                  "Notification does not contain valid \"jsonrpc\" field".to_string(),
291              ))
292          }
293  
294          if !map.contains_key("method") || !map["method"].is_string() {
295              return Err(RpcError::InvalidJson(
296                  "Notification does not contain valid \"method\" field".to_string(),
297              ))
298          }
299  
300          if !map.contains_key("params") {
301              return Err(RpcError::InvalidJson(
302                  "Notification does not contain valid \"params\" field".to_string(),
303              ))
304          }
305  
306          if !map["params"].is_object() && !map["params"].is_array() {
307              return Err(RpcError::InvalidJson(
308                  "Request does not contain valid \"params\" field".to_string(),
309              ))
310          }
311  
312          Ok(Self {
313              jsonrpc: "2.0",
314              method: map["method"].get::<String>().unwrap().clone(),
315              params: map["params"].clone(),
316          })
317      }
318  }
319  
320  /// A JSON-RPC response object
321  #[derive(Clone, Debug)]
322  pub struct JsonResponse {
323      /// JSON-RPC version
324      pub jsonrpc: &'static str,
325      /// Request ID
326      pub id: u16,
327      /// Response result
328      pub result: JsonValue,
329  }
330  
331  impl JsonResponse {
332      /// Create a new [`JsonResponse`] object with the given ID and result value.
333      /// Creating a `JsonResponse` implies that the method call was successful.
334      pub fn new(result: JsonValue, id: u16) -> Self {
335          Self { jsonrpc: "2.0", id, result }
336      }
337  
338      /// Convert the object into a JSON string
339      pub fn stringify(&self) -> Result<String> {
340          let v: JsonValue = self.into();
341          Ok(v.stringify()?)
342      }
343  }
344  
345  impl From<&JsonResponse> for JsonValue {
346      fn from(rep: &JsonResponse) -> JsonValue {
347          JsonValue::Object(HashMap::from([
348              ("jsonrpc".to_string(), JsonValue::String(rep.jsonrpc.to_string())),
349              ("id".to_string(), JsonValue::Number(rep.id.into())),
350              ("result".to_string(), rep.result.clone()),
351          ]))
352      }
353  }
354  
355  impl TryFrom<&JsonValue> for JsonResponse {
356      type Error = RpcError;
357  
358      fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
359          if !value.is_object() {
360              return Err(RpcError::InvalidJson("Json is not an Object".to_string()))
361          }
362  
363          let map: &HashMap<String, JsonValue> = value.get().unwrap();
364  
365          if !map.contains_key("jsonrpc") ||
366              !map["jsonrpc"].is_string() ||
367              map["jsonrpc"] != JsonValue::String("2.0".to_string())
368          {
369              return Err(RpcError::InvalidJson(
370                  "Response does not contain valid \"jsonrpc\" field".to_string(),
371              ))
372          }
373  
374          if !map.contains_key("id") || !map["id"].is_number() {
375              return Err(RpcError::InvalidJson(
376                  "Response does not contain valid \"id\" field".to_string(),
377              ))
378          }
379  
380          if !map.contains_key("result") {
381              return Err(RpcError::InvalidJson(
382                  "Response does not contain valid \"result\" field".to_string(),
383              ))
384          }
385  
386          Ok(Self {
387              jsonrpc: "2.0",
388              id: *map["id"].get::<f64>().unwrap() as u16,
389              result: map["result"].clone(),
390          })
391      }
392  }
393  
394  impl TryFrom<JsonResult> for JsonResponse {
395      type Error = RpcError;
396  
397      /// Converts [`JsonResult`] to [`JsonResponse`], returning the response or an `InvalidJson`
398      /// error if the structure is not a `JsonResponse`.
399      fn try_from(result: JsonResult) -> std::result::Result<Self, Self::Error> {
400          match result {
401              JsonResult::Response(response) => Ok(response),
402              _ => Err(RpcError::InvalidJson("Not a JsonResult::Response".to_string())),
403          }
404      }
405  }
406  
407  /// A JSON-RPC error object
408  #[derive(Clone, Debug)]
409  pub struct JsonError {
410      /// JSON-RPC version
411      pub jsonrpc: &'static str,
412      /// Request ID
413      pub id: u16,
414      /// JSON-RPC error (code and message)
415      pub error: JsonErrorVal,
416  }
417  
418  /// A JSON-RPC error value (code and message)
419  #[derive(Clone, Debug)]
420  pub struct JsonErrorVal {
421      /// Error code
422      pub code: i32,
423      /// Error message
424      pub message: String,
425  }
426  
427  impl JsonError {
428      /// Create a new [`JsonError`] object with the given error code, optional
429      /// message, and a response ID.
430      /// Creating a `JsonError` implies that the method call was unsuccessful.
431      pub fn new(c: ErrorCode, message: Option<String>, id: u16) -> Self {
432          let error = JsonErrorVal { code: c.code(), message: message.unwrap_or(c.message()) };
433          Self { jsonrpc: "2.0", id, error }
434      }
435  
436      /// Convert the object into a JSON string
437      pub fn stringify(&self) -> Result<String> {
438          let v: JsonValue = self.into();
439          Ok(v.stringify()?)
440      }
441  }
442  
443  impl From<&JsonError> for JsonValue {
444      fn from(err: &JsonError) -> JsonValue {
445          let errmap = JsonValue::Object(HashMap::from([
446              ("code".to_string(), JsonValue::Number(err.error.code.into())),
447              ("message".to_string(), JsonValue::String(err.error.message.clone())),
448          ]));
449  
450          JsonValue::Object(HashMap::from([
451              ("jsonrpc".to_string(), JsonValue::String(err.jsonrpc.to_string())),
452              ("id".to_string(), JsonValue::Number(err.id.into())),
453              ("error".to_string(), errmap),
454          ]))
455      }
456  }
457  
458  impl TryFrom<JsonResult> for JsonError {
459      type Error = RpcError;
460  
461      /// Converts [`JsonResult`] to [`JsonError`], returning the response or an `InvalidJson`
462      /// error if the structure is not a `JsonError`.
463      fn try_from(result: JsonResult) -> std::result::Result<Self, Self::Error> {
464          match result {
465              JsonResult::Error(error) => Ok(error),
466              _ => Err(RpcError::InvalidJson("Not a JsonResult::Error".to_string())),
467          }
468      }
469  }
470  
471  impl TryFrom<&JsonValue> for JsonError {
472      type Error = RpcError;
473  
474      fn try_from(value: &JsonValue) -> std::result::Result<Self, Self::Error> {
475          if !value.is_object() {
476              return Err(RpcError::InvalidJson("JSON is not an Object".to_string()))
477          }
478  
479          let map: &HashMap<String, JsonValue> = value.get().unwrap();
480  
481          if !map.contains_key("jsonrpc") ||
482              !map["jsonrpc"].is_string() ||
483              map["jsonrpc"] != JsonValue::String("2.0".to_string())
484          {
485              return Err(RpcError::InvalidJson(
486                  "Error does not contain valid \"jsonrpc\" field".to_string(),
487              ))
488          }
489  
490          if !map.contains_key("id") || !map["id"].is_number() {
491              return Err(RpcError::InvalidJson(
492                  "Error does not contain valid \"id\" field".to_string(),
493              ))
494          }
495  
496          if !map.contains_key("error") || !map["error"].is_object() {
497              return Err(RpcError::InvalidJson(
498                  "Error does not contain valid \"error\" field".to_string(),
499              ))
500          }
501  
502          if !map["error"]["code"].is_number() {
503              return Err(RpcError::InvalidJson(
504                  "Error does not contain valid \"error.code\" field".to_string(),
505              ))
506          }
507  
508          if !map["error"]["message"].is_string() {
509              return Err(RpcError::InvalidJson(
510                  "Error does not contain valid \"error.message\" field".to_string(),
511              ))
512          }
513  
514          Ok(Self {
515              jsonrpc: "2.0",
516              id: *map["id"].get::<f64>().unwrap() as u16,
517              error: JsonErrorVal {
518                  code: *map["error"]["code"].get::<f64>().unwrap() as i32,
519                  message: map["error"]["message"].get::<String>().unwrap().to_string(),
520              },
521          })
522      }
523  }
524  
525  /// A JSON-RPC subscriber for notifications
526  #[derive(Clone, Debug)]
527  pub struct JsonSubscriber {
528      /// Notification method
529      pub method: &'static str,
530      /// Notification publisher
531      pub publisher: PublisherPtr<JsonNotification>,
532  }
533  
534  impl JsonSubscriber {
535      pub fn new(method: &'static str) -> Self {
536          let publisher = Publisher::new();
537          Self { method, publisher }
538      }
539  
540      /// Send a notification to the publisher with the given JSON object
541      pub async fn notify(&self, params: JsonValue) {
542          let notification = JsonNotification::new(self.method, params);
543          self.publisher.notify(notification).await;
544      }
545  }
546  
547  /// Parses a [`JsonValue`] parameter into a `String`.
548  /// Returns the string if successful or an error if the value is not a valid string.
549  pub fn parse_json_string(name: &str, value: &JsonValue) -> std::result::Result<String, RpcError> {
550      value
551          .get::<String>()
552          .cloned()
553          .ok_or_else(|| RpcError::InvalidJson(format!("Parameter '{name}' is not a valid string")))
554  }
555  
556  /// Parses a [`JsonValue`] parameter into a `f64`.
557  /// Returns the number if successful or an error if the value is not a valid number.
558  pub fn parse_json_number(name: &str, value: &JsonValue) -> std::result::Result<f64, RpcError> {
559      value.get::<f64>().cloned().ok_or_else(|| {
560          RpcError::InvalidJson(format!("Parameter '{name}' is not a supported number type"))
561      })
562  }
563  
564  /// Parses the element at the specified index in a [`JsonValue::Array`] into a
565  /// string. Returns the string if successful, or an error if the parameter is
566  /// missing, not an array, or not a valid string.
567  pub fn parse_json_array_string(
568      name: &str,
569      index: usize,
570      array_value: &JsonValue,
571  ) -> std::result::Result<String, RpcError> {
572      match array_value {
573          JsonValue::Array(values) => values
574              .get(index)
575              .ok_or_else(|| {
576                  RpcError::InvalidJson(format!("Parameter '{name}' at index {index} is missing"))
577              })
578              .and_then(|param| parse_json_string(name, param)),
579          _ => Err(RpcError::InvalidJson(format!("Parameter '{name}' is not an array"))),
580      }
581  }
582  
583  /// Parses the element at the specified index in a [`JsonValue::Array`] into an
584  /// `f64` (compatible with [`JsonValue::Number`]). Returns the number if successful,
585  /// or an error if the parameter is missing, not an array, or is not a valid number.
586  pub fn parse_json_array_number(
587      name: &str,
588      index: usize,
589      array_value: &JsonValue,
590  ) -> std::result::Result<f64, RpcError> {
591      match array_value {
592          JsonValue::Array(values) => values
593              .get(index)
594              .ok_or_else(|| {
595                  RpcError::InvalidJson(format!("Parameter '{name}' at index {index} is missing"))
596              })
597              .and_then(|param| parse_json_number(name, param)),
598          _ => Err(RpcError::InvalidJson(format!("Parameter '{name}' is not an array"))),
599      }
600  }
601  
602  /// Attempts to parse a `JsonResult`, converting it into a `JsonResponse` and
603  /// extracting a string result from it. Returns an error if conversion or
604  /// extraction fails, and the extracted string on success.
605  pub fn parse_json_response_string(
606      json_result: JsonResult,
607  ) -> std::result::Result<String, RpcError> {
608      // Try converting `JsonResult` into a `JsonResponse`.
609      let json_response: JsonResponse = json_result.try_into().map_err(|_| {
610          RpcError::InvalidJson("Failed to convert JsonResult into JsonResponse".to_string())
611      })?;
612  
613      // Attempt to extract a string result from the JsonResponse
614      json_response.result.get::<String>().map(|value| value.to_string()).ok_or_else(|| {
615          RpcError::InvalidJson("Failed to parse string from JsonResponse result".to_string())
616      })
617  }
618  
619  /// Converts the provided JSON-RPC parameters into an array of JSON values,
620  /// returning a reference to the array if successful, or a JsonResult error containing a
621  /// JsonError when the input is not a JSON array.
622  pub fn to_json_array(params: &JsonValue) -> std::result::Result<&Vec<JsonValue>, RpcError> {
623      if let JsonValue::Array(array) = params {
624          Ok(array)
625      } else {
626          Err(RpcError::InvalidJson(
627              "Expected an array of values, but received a different JSON type.".to_string(),
628          ))
629      }
630  }
631  
632  /// Validates whether the provided JSON parameter is an empty array or object, returning success if it is empty or an Error if it contains values.
633  pub fn validate_empty_params(params: &JsonValue) -> std::result::Result<(), RpcError> {
634      match to_json_array(params) {
635          Ok(array) if array.is_empty() => Ok(()),
636          Ok(_) => Err(RpcError::InvalidJson(format!(
637              "Parameters not permited, received: {:?}",
638              params.stringify().unwrap_or("Error converting JSON to string".to_string())
639          ))),
640          Err(err) => Err(RpcError::InvalidJson(err.to_string())),
641      }
642  }