Skip to content

Commit

Permalink
refactor: remove duplicate JsonRpcError and Response structs
Browse files Browse the repository at this point in the history
  • Loading branch information
aatifsyed committed Mar 19, 2024
1 parent 8c9936d commit bda8bb5
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 156 deletions.
2 changes: 1 addition & 1 deletion src/cli/subcommands/auth_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn process_perms(perm: String) -> Result<Vec<String>, JsonRpcError> {
"sign" => SIGN,
"write" => WRITE,
"read" => READ,
_ => return Err(JsonRpcError::INVALID_PARAMS),
_ => return Err(JsonRpcError::invalid_params("unknown permission", None)),
}
.iter()
.map(ToString::to_string)
Expand Down
62 changes: 44 additions & 18 deletions src/rpc/error.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,66 @@
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use std::fmt::Display;
use std::fmt::{self, Display};

use jsonrpsee::types::error::{
ErrorObjectOwned, INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, PARSE_ERROR_CODE,
};
use jsonrpsee::types::error::{self, ErrorCode, ErrorObjectOwned};

#[derive(derive_more::From, derive_more::Into, Debug)]
#[derive(derive_more::From, derive_more::Into, Debug, PartialEq)]
pub struct JsonRpcError {
inner: ErrorObjectOwned,
}

impl JsonRpcError {
fn new(code: i32, message: impl Display, data: impl Into<Option<serde_json::Value>>) -> Self {
pub fn new(
code: i32,
message: impl Display,
data: impl Into<Option<serde_json::Value>>,
) -> Self {
Self {
inner: ErrorObjectOwned::owned(code, message.to_string(), data.into()),
}
}
pub fn parse_error(message: impl Display, data: impl Into<Option<serde_json::Value>>) -> Self {
Self::new(PARSE_ERROR_CODE, message, data)
pub fn message(&self) -> &str {
self.inner.message()
}
pub fn internal_error(
message: impl Display,
data: impl Into<Option<serde_json::Value>>,
) -> Self {
Self::new(INTERNAL_ERROR_CODE, message, data)
pub fn known_code(&self) -> ErrorCode {
self.inner.code().into()
}
pub fn invalid_params(
message: impl Display,
data: impl Into<Option<serde_json::Value>>,
) -> Self {
Self::new(INVALID_PARAMS_CODE, message, data)
}

impl Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("JSON-RPC error:\n")?;
f.write_fmt(format_args!("\tcode: {}\n", self.inner.code()))?;
f.write_fmt(format_args!("\tmessage: {}\n", self.inner.message()))?;
if let Some(data) = self.inner.data() {
f.write_fmt(format_args!("\tdata: {}\n", data))?
}
Ok(())
}
}

impl std::error::Error for JsonRpcError {}

macro_rules! ctor {
($($ctor:ident { $code:expr })*) => {
$(
impl JsonRpcError {
pub fn $ctor(message: impl Display, data: impl Into<Option<serde_json::Value>>) -> Self {
Self::new($code, message, data)
}
}
)*
}
}

ctor! {
parse_error { error::PARSE_ERROR_CODE }
internal_error { error::INTERNAL_ERROR_CODE }
invalid_params { error::INVALID_PARAMS_CODE }
method_not_found { error::METHOD_NOT_FOUND_CODE }
}

macro_rules! from2internal {
($($ty:ty),* $(,)?) => {
$(
Expand Down
4 changes: 3 additions & 1 deletion src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ mod beacon_api;
mod chain_api;
mod channel;
mod common_api;
mod error;
mod eth_api;
mod gas_api;
mod mpool_api;
Expand All @@ -18,6 +17,9 @@ mod state_api;
mod sync_api;
mod wallet_api;

mod error;
pub use error::JsonRpcError;

use std::net::SocketAddr;
use std::sync::Arc;

Expand Down
174 changes: 50 additions & 124 deletions src/rpc_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub mod state_ops;
pub mod sync_ops;
pub mod wallet_ops;

use std::borrow::Cow;
use std::env;
use std::fmt;
use std::marker::PhantomData;
Expand All @@ -22,12 +21,14 @@ use std::time::Duration;

use crate::libp2p::{Multiaddr, Protocol};
use crate::lotus_json::HasLotusJson;
pub use crate::rpc::JsonRpcError;
use crate::utils::net::global_http_client;
use jsonrpsee::{
core::{client::ClientT, traits::ToRpcParams},
types::{params::TwoPointZero, Id, Request},
types::{Id, Request},
ws_client::WsClientBuilder,
};
use serde::de::IntoDeserializer;
use serde::Deserialize;
use tracing::debug;

Expand Down Expand Up @@ -92,7 +93,7 @@ impl ApiInfo {

pub async fn call<T: HasLotusJson>(&self, req: RpcRequest<T>) -> Result<T, JsonRpcError> {
let params = serde_json::value::to_raw_value(&req.params)
.map_err(|_| JsonRpcError::INVALID_PARAMS)?;
.map_err(|e| JsonRpcError::invalid_params(e, None))?;
let rpc_req = Request::new(req.method_name.into(), Some(&params), Id::Number(0));

let api_url = multiaddress_to_url(&self.multiaddr, req.rpc_endpoint).to_string();
Expand All @@ -109,33 +110,40 @@ impl ApiInfo {
};

let response = request.send().await?;
if response.status() == http0::StatusCode::NOT_FOUND {
return Err(JsonRpcError::METHOD_NOT_FOUND);
}
if response.status() == http0::StatusCode::FORBIDDEN {
let msg = if self.token.is_none() {
"Permission denied: Token required."
} else {
"Permission denied: Insufficient rights."
};
return Err(JsonRpcError {
code: response.status().as_u16() as i64,
message: Cow::Borrowed(msg),
});
}
if !response.status().is_success() {
return Err(JsonRpcError {
code: response.status().as_u16() as i64,
message: Cow::Owned(response.text().await?),
});
}
let json = response.bytes().await?;
let rpc_res: JsonRpcResponse<T::LotusJson> =
serde_json::from_slice(&json).map_err(|_| JsonRpcError::PARSE_ERROR)?;

match rpc_res {
JsonRpcResponse::Result { result, .. } => Ok(HasLotusJson::from_lotus_json(result)),
JsonRpcResponse::Error { error, .. } => Err(error),
match response.status() {
http0::StatusCode::NOT_FOUND => {
Err(JsonRpcError::method_not_found("method_not_found", None))
}
http0::StatusCode::FORBIDDEN => Err(JsonRpcError::new(
response.status().as_u16().into(),
match &self.token {
Some(_) => "Permission denied: Insufficient rights.",
None => "Permission denied: Token required.",
},
None,
)),
other if !other.is_success() => Err(JsonRpcError::new(
other.as_u16().into(),
response.text().await?,
None,
)),
_ok => {
let bytes = response.bytes().await?;
let response = serde_json::from_slice::<
jsonrpsee::types::Response<&serde_json::value::RawValue>,
>(&bytes)
.map_err(|e| JsonRpcError::parse_error(e, None))?;
match response.payload {
jsonrpsee::types::ResponsePayload::Success(it) => {
T::LotusJson::deserialize(it.into_deserializer())
.map(T::from_lotus_json)
.map_err(|e| JsonRpcError::parse_error(e, None))
}
jsonrpsee::types::ResponsePayload::Error(e) => {
Err(JsonRpcError::parse_error(e, None))
}
}
}
}
}

Expand All @@ -148,108 +156,26 @@ impl ApiInfo {
let ws_client = WsClientBuilder::default()
.request_timeout(req.timeout)
.build(api_url.to_string())
.await?;
let response_lotus_json: T::LotusJson = ws_client.request(req.method_name, req).await?;
.await
.map_err(|e| JsonRpcError::internal_error(e, None))?;
let response_lotus_json: T::LotusJson = ws_client
.request(req.method_name, req)
.await
.map_err(|e| JsonRpcError::internal_error(e, None))?;
Ok(HasLotusJson::from_lotus_json(response_lotus_json))
}
}

/// Error object in a response
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct JsonRpcError {
pub code: i64,
pub message: Cow<'static, str>,
}

impl JsonRpcError {
// https://www.jsonrpc.org/specification#error_object
// -32700 Parse error Invalid JSON was received by the server.
// An error occurred on the server while parsing the JSON text.
// -32600 Invalid Request The JSON sent is not a valid Request object.
// -32601 Method not found The method does not exist / is not available.
// -32602 Invalid params Invalid method parameter(s).
// -32603 Internal error Internal JSON-RPC error.
// -32000 to -32099 Server error Reserved for implementation-defined server-errors.
pub const PARSE_ERROR: JsonRpcError = JsonRpcError {
code: -32700,
message: Cow::Borrowed(
"Invalid JSON was received by the server. \
An error occurred on the server while parsing the JSON text.",
),
};
pub const INVALID_REQUEST: JsonRpcError = JsonRpcError {
code: -32600,
message: Cow::Borrowed("The JSON sent is not a valid Request object."),
};
pub const METHOD_NOT_FOUND: JsonRpcError = JsonRpcError {
code: -32601,
message: Cow::Borrowed("The method does not exist / is not available."),
};
pub const INVALID_PARAMS: JsonRpcError = JsonRpcError {
code: -32602,
message: Cow::Borrowed("Invalid method parameter(s)."),
};
pub const TIMED_OUT: JsonRpcError = JsonRpcError {
code: 0,
message: Cow::Borrowed("Operation timed out."),
};
}

impl std::fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} (code={})", self.message, self.code)
}
}

impl std::error::Error for JsonRpcError {
fn description(&self) -> &str {
&self.message
}
}

impl From<reqwest::Error> for JsonRpcError {
fn from(reqwest_error: reqwest::Error) -> Self {
JsonRpcError {
code: reqwest_error
.status()
.map(|s| s.as_u16())
.unwrap_or_default() as i64,
message: Cow::Owned(reqwest_error.to_string()),
}
}
}

impl From<jsonrpsee::core::client::Error> for JsonRpcError {
fn from(error: jsonrpsee::core::client::Error) -> Self {
use jsonrpsee::core::client::Error::*;

match error {
RequestTimeout => JsonRpcError::TIMED_OUT,
e => JsonRpcError {
code: 500,
message: Cow::Owned(e.to_string()),
},
}
fn from(e: reqwest::Error) -> Self {
Self::new(
e.status().map(|it| it.as_u16()).unwrap_or_default().into(),
e,
None,
)
}
}

#[derive(Deserialize)]
#[serde(untagged)]
pub enum JsonRpcResponse<'a, R> {
Result {
jsonrpc: TwoPointZero,
result: R,
#[serde(borrow)]
id: Id<'a>,
},
Error {
jsonrpc: TwoPointZero,
error: JsonRpcError,
#[serde(borrow)]
id: Id<'a>,
},
}

struct Url {
protocol: String,
port: u16,
Expand Down
22 changes: 11 additions & 11 deletions src/tool/subcommands/api_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use fil_actors_shared::v10::runtime::DomainSeparationTag;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use fvm_ipld_blockstore::Blockstore;
use jsonrpsee::types::ErrorCode;
use serde::de::DeserializeOwned;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::Path;
Expand Down Expand Up @@ -199,17 +200,16 @@ enum EndpointStatus {

impl EndpointStatus {
fn from_json_error(err: JsonRpcError) -> Self {
if err.code == JsonRpcError::INVALID_REQUEST.code {
EndpointStatus::InvalidRequest
} else if err.code == JsonRpcError::METHOD_NOT_FOUND.code {
EndpointStatus::MissingMethod
} else if err.code == JsonRpcError::PARSE_ERROR.code {
EndpointStatus::InvalidResponse
} else if err.code == 0 && err.message.contains("timed out") {
EndpointStatus::Timeout
} else {
tracing::debug!("{err}");
EndpointStatus::InternalServerError
match err.known_code() {
ErrorCode::ParseError => Self::InvalidResponse,
ErrorCode::OversizedRequest => Self::InvalidRequest,
ErrorCode::InvalidRequest => Self::InvalidRequest,
ErrorCode::MethodNotFound => Self::MissingMethod,
it if it.code() == 0 && it.message().contains("timed out") => Self::Timeout,
_ => {
tracing::debug!(?err);
Self::InternalServerError
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/tool/subcommands/shed_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl ShedCommands {
multiaddr: host,
token: None,
};
let head = client.chain_head().await.context("couldn't get HEAD")?;
let head = client.chain_head().await?;
let end_height = match height {
Some(it) => it,
None => head
Expand Down

0 comments on commit bda8bb5

Please sign in to comment.