/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ //! JSON-RPC 2.0 object definitions use std::collections::HashMap; use rand::{rngs::OsRng, Rng}; use tinyjson::JsonValue; use crate::{ error::RpcError, system::{Subscriber, SubscriberPtr}, Result, }; /// JSON-RPC error codes. /// The error codes `[-32768, -32000]` are reserved for predefined errors. #[derive(Copy, Clone, Debug)] pub enum ErrorCode { /// Invalid JSON was received by the server. /// An error occurred on the server while parsing the JSON text. ParseError, /// The JSON sent is not a valid Request object. InvalidRequest, /// The method does not exist / is not available. MethodNotFound, /// Invalid method parameter(s). InvalidParams, /// Internal JSON-RPC error. InternalError, /// ID mismatch IdMismatch, /// Invalid/Unexpected reply InvalidReply, /// Reserved for implementation-defined server-errors. ServerError(i32), } impl ErrorCode { pub fn code(&self) -> i32 { match *self { Self::ParseError => -32700, Self::InvalidRequest => -32600, Self::MethodNotFound => -32601, Self::InvalidParams => -32602, Self::InternalError => -32603, Self::IdMismatch => -32360, Self::InvalidReply => -32361, Self::ServerError(c) => c, } } pub fn message(&self) -> String { match *self { Self::ParseError => "parse error".to_string(), Self::InvalidRequest => "invalid request".to_string(), Self::MethodNotFound => "method not found".to_string(), Self::InvalidParams => "invalid params".to_string(), Self::InternalError => "internal error".to_string(), Self::IdMismatch => "id mismatch".to_string(), Self::InvalidReply => "invalid reply".to_string(), Self::ServerError(_) => "server error".to_string(), } } pub fn desc(&self) -> JsonValue { JsonValue::String(self.message()) } } // ANCHOR: jsonresult /// Wrapping enum around the available JSON-RPC object types #[derive(Clone, Debug)] pub enum JsonResult { Response(JsonResponse), Error(JsonError), Notification(JsonNotification), /// Subscriber is a special object that yields a channel Subscriber(JsonSubscriber), SubscriberWithReply(JsonSubscriber, JsonResponse), Request(JsonRequest), } impl JsonResult { pub fn try_from_value(value: &JsonValue) -> Result { if let Ok(response) = JsonResponse::try_from(value) { return Ok(Self::Response(response)) } if let Ok(error) = JsonError::try_from(value) { return Ok(Self::Error(error)) } if let Ok(notification) = JsonNotification::try_from(value) { return Ok(Self::Notification(notification)) } Err(RpcError::InvalidJson("Invalid JSON Result".to_string()).into()) } } impl From for JsonResult { fn from(resp: JsonResponse) -> Self { Self::Response(resp) } } impl From for JsonResult { fn from(err: JsonError) -> Self { Self::Error(err) } } impl From for JsonResult { fn from(notif: JsonNotification) -> Self { Self::Notification(notif) } } impl From for JsonResult { fn from(sub: JsonSubscriber) -> Self { Self::Subscriber(sub) } } impl From<(JsonSubscriber, JsonResponse)> for JsonResult { fn from(tuple: (JsonSubscriber, JsonResponse)) -> Self { Self::SubscriberWithReply(tuple.0, tuple.1) } } // ANCHOR: jsonrequest /// A JSON-RPC request object #[derive(Clone, Debug)] pub struct JsonRequest { /// JSON-RPC version pub jsonrpc: &'static str, /// Request ID pub id: u16, /// Request method pub method: String, /// Request parameters pub params: JsonValue, } // ANCHOR_END: jsonrequest impl JsonRequest { /// Create a new [`JsonRequest`] object with the given method and parameters. /// The request ID is chosen randomly. pub fn new(method: &str, params: JsonValue) -> Self { assert!(params.is_object() || params.is_array()); Self { jsonrpc: "2.0", id: OsRng::gen(&mut OsRng), method: method.to_string(), params } } /// Convert the object into a JSON string pub fn stringify(&self) -> Result { let v: JsonValue = self.into(); Ok(v.stringify()?) } } impl From<&JsonRequest> for JsonValue { fn from(req: &JsonRequest) -> JsonValue { JsonValue::Object(HashMap::from([ ("jsonrpc".to_string(), JsonValue::String(req.jsonrpc.to_string())), ("id".to_string(), JsonValue::Number(req.id.into())), ("method".to_string(), JsonValue::String(req.method.clone())), ("params".to_string(), req.params.clone()), ])) } } impl TryFrom<&JsonValue> for JsonRequest { type Error = RpcError; fn try_from(value: &JsonValue) -> std::result::Result { if !value.is_object() { return Err(RpcError::InvalidJson("JSON is not an Object".to_string())) } let map: &HashMap = value.get().unwrap(); if !map.contains_key("jsonrpc") || !map["jsonrpc"].is_string() || map["jsonrpc"] != JsonValue::String("2.0".to_string()) { return Err(RpcError::InvalidJson( "Request does not contain valid \"jsonrpc\" field".to_string(), )) } if !map.contains_key("id") || !map["id"].is_number() { return Err(RpcError::InvalidJson( "Request does not contain valid \"id\" field".to_string(), )) } if !map.contains_key("method") || !map["method"].is_string() { return Err(RpcError::InvalidJson( "Request does not contain valid \"method\" field".to_string(), )) } if !map.contains_key("params") { return Err(RpcError::InvalidJson( "Request does not contain valid \"params\" field".to_string(), )) } if !map["params"].is_object() && !map["params"].is_array() { return Err(RpcError::InvalidJson( "Request does not contain valid \"params\" field".to_string(), )) } Ok(Self { jsonrpc: "2.0", id: *map["id"].get::().unwrap() as u16, method: map["method"].get::().unwrap().clone(), params: map["params"].clone(), }) } } /// A JSON-RPC notification object #[derive(Clone, Debug)] pub struct JsonNotification { /// JSON-RPC version pub jsonrpc: &'static str, /// Notification method pub method: String, /// Notification parameters pub params: JsonValue, } impl JsonNotification { /// Create a new [`JsonNotification`] object with the given method and parameters. pub fn new(method: &str, params: JsonValue) -> Self { assert!(params.is_object() || params.is_array()); Self { jsonrpc: "2.0", method: method.to_string(), params } } /// Convert the object into a JSON string pub fn stringify(&self) -> Result { let v: JsonValue = self.into(); Ok(v.stringify()?) } } impl From<&JsonNotification> for JsonValue { fn from(notif: &JsonNotification) -> JsonValue { JsonValue::Object(HashMap::from([ ("jsonrpc".to_string(), JsonValue::String(notif.jsonrpc.to_string())), ("method".to_string(), JsonValue::String(notif.method.clone())), ("params".to_string(), notif.params.clone()), ])) } } impl TryFrom<&JsonValue> for JsonNotification { type Error = RpcError; fn try_from(value: &JsonValue) -> std::result::Result { if !value.is_object() { return Err(RpcError::InvalidJson("JSON is not an Object".to_string())) } let map: &HashMap = value.get().unwrap(); if !map.contains_key("jsonrpc") || !map["jsonrpc"].is_string() || map["jsonrpc"] != JsonValue::String("2.0".to_string()) { return Err(RpcError::InvalidJson( "Notification does not contain valid \"jsonrpc\" field".to_string(), )) } if !map.contains_key("method") || !map["method"].is_string() { return Err(RpcError::InvalidJson( "Notification does not contain valid \"method\" field".to_string(), )) } if !map.contains_key("params") { return Err(RpcError::InvalidJson( "Notification does not contain valid \"params\" field".to_string(), )) } if !map["params"].is_object() && !map["params"].is_array() { return Err(RpcError::InvalidJson( "Request does not contain valid \"params\" field".to_string(), )) } Ok(Self { jsonrpc: "2.0", method: map["method"].get::().unwrap().clone(), params: map["params"].clone(), }) } } /// A JSON-RPC response object #[derive(Clone, Debug)] pub struct JsonResponse { /// JSON-RPC version pub jsonrpc: &'static str, /// Request ID pub id: u16, /// Response result pub result: JsonValue, } impl JsonResponse { /// Create a new [`JsonResponse`] object with the given ID and result value. /// Creating a `JsonResponse` implies that the method call was successful. pub fn new(result: JsonValue, id: u16) -> Self { Self { jsonrpc: "2.0", id, result } } /// Convert the object into a JSON string pub fn stringify(&self) -> Result { let v: JsonValue = self.into(); Ok(v.stringify()?) } } impl From<&JsonResponse> for JsonValue { fn from(rep: &JsonResponse) -> JsonValue { JsonValue::Object(HashMap::from([ ("jsonrpc".to_string(), JsonValue::String(rep.jsonrpc.to_string())), ("id".to_string(), JsonValue::Number(rep.id.into())), ("result".to_string(), rep.result.clone()), ])) } } impl TryFrom<&JsonValue> for JsonResponse { type Error = RpcError; fn try_from(value: &JsonValue) -> std::result::Result { if !value.is_object() { return Err(RpcError::InvalidJson("Json is not an Object".to_string())) } let map: &HashMap = value.get().unwrap(); if !map.contains_key("jsonrpc") || !map["jsonrpc"].is_string() || map["jsonrpc"] != JsonValue::String("2.0".to_string()) { return Err(RpcError::InvalidJson( "Response does not contain valid \"jsonrpc\" field".to_string(), )) } if !map.contains_key("id") || !map["id"].is_number() { return Err(RpcError::InvalidJson( "Response does not contain valid \"id\" field".to_string(), )) } if !map.contains_key("result") { return Err(RpcError::InvalidJson( "Response does not contain valid \"result\" field".to_string(), )) } Ok(Self { jsonrpc: "2.0", id: *map["id"].get::().unwrap() as u16, result: map["result"].clone(), }) } } /// A JSON-RPC error object #[derive(Clone, Debug)] pub struct JsonError { /// JSON-RPC version pub jsonrpc: &'static str, /// Request ID pub id: u16, /// JSON-RPC error (code and message) pub error: JsonErrorVal, } /// A JSON-RPC error value (code and message) #[derive(Clone, Debug)] pub struct JsonErrorVal { /// Error code pub code: i32, /// Error message pub message: String, } impl JsonError { /// Create a new [`JsonError`] object with the given error code, optional /// message, and a response ID. /// Creating a `JsonError` implies that the method call was unsuccessful. pub fn new(c: ErrorCode, message: Option, id: u16) -> Self { let error = JsonErrorVal { code: c.code(), message: message.unwrap_or(c.message()) }; Self { jsonrpc: "2.0", id, error } } /// Convert the object into a JSON string pub fn stringify(&self) -> Result { let v: JsonValue = self.into(); Ok(v.stringify()?) } } impl From<&JsonError> for JsonValue { fn from(err: &JsonError) -> JsonValue { let errmap = JsonValue::Object(HashMap::from([ ("code".to_string(), JsonValue::Number(err.error.code.into())), ("message".to_string(), JsonValue::String(err.error.message.clone())), ])); JsonValue::Object(HashMap::from([ ("jsonrpc".to_string(), JsonValue::String(err.jsonrpc.to_string())), ("id".to_string(), JsonValue::Number(err.id.into())), ("error".to_string(), errmap), ])) } } impl TryFrom<&JsonValue> for JsonError { type Error = RpcError; fn try_from(value: &JsonValue) -> std::result::Result { if !value.is_object() { return Err(RpcError::InvalidJson("JSON is not an Object".to_string())) } let map: &HashMap = value.get().unwrap(); if !map.contains_key("jsonrpc") || !map["jsonrpc"].is_string() || map["jsonrpc"] != JsonValue::String("2.0".to_string()) { return Err(RpcError::InvalidJson( "Error does not contain valid \"jsonrpc\" field".to_string(), )) } if !map.contains_key("id") || !map["id"].is_number() { return Err(RpcError::InvalidJson( "Error does not contain valid \"id\" field".to_string(), )) } if !map.contains_key("error") || !map["error"].is_object() { return Err(RpcError::InvalidJson( "Error does not contain valid \"error\" field".to_string(), )) } if !map["error"]["code"].is_number() { return Err(RpcError::InvalidJson( "Error does not contain valid \"error.code\" field".to_string(), )) } if !map["error"]["message"].is_string() { return Err(RpcError::InvalidJson( "Error does not contain valid \"error.message\" field".to_string(), )) } Ok(Self { jsonrpc: "2.0", id: *map["id"].get::().unwrap() as u16, error: JsonErrorVal { code: *map["error"]["code"].get::().unwrap() as i32, message: map["error"]["message"].get::().unwrap().to_string(), }, }) } } /// A JSON-RPC subscriber for notifications #[derive(Clone, Debug)] pub struct JsonSubscriber { /// Notification method pub method: &'static str, /// Notification subscriber pub sub: SubscriberPtr, } impl JsonSubscriber { pub fn new(method: &'static str) -> Self { let sub = Subscriber::new(); Self { method, sub } } /// Send a notification to the subscriber with the given JSON object pub async fn notify(&self, params: JsonValue) { let notification = JsonNotification::new(self.method, params); self.sub.notify(notification).await; } }