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 }