#[cfg(feature = "reqwest")]
mod reqwest;
#[cfg(feature = "surf")]
pub mod surf;
#[cfg(feature = "isahc")]
mod isahc;
#[cfg(feature = "macros")]
pub use jsonrpc_client_macro::api;
pub mod export {
pub use async_trait;
pub use serde;
}
#[cfg(feature = "macros")]
pub use jsonrpc_client_macro::implement;
pub use url::Url;
use serde::{de::DeserializeOwned, ser::SerializeStruct, Deserialize, Serialize, Serializer};
use serde_json::Value;
use std::{
error::Error as StdError,
fmt::{self, Debug},
result::Result,
};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum Id {
Number(i64),
String(String),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum Version {
#[serde(rename = "1.0")]
V1,
#[serde(rename = "2.0")]
V2,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Request {
pub id: Id,
pub jsonrpc: Version,
pub method: String,
pub params: Params,
}
#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum Params {
ByPosition(Vec<serde_json::Value>),
ByName(serde_json::Map<String, serde_json::Value>),
}
impl Request {
pub fn new_v1(method: &str) -> Self {
Self {
id: Id::Number(0),
jsonrpc: Version::V1,
method: method.to_owned(),
params: Params::ByPosition(vec![]),
}
}
pub fn new_v2(method: &str) -> Self {
Self {
id: Id::Number(0),
jsonrpc: Version::V2,
method: method.to_owned(),
params: Params::ByName(serde_json::Map::new()),
}
}
pub fn with_argument<T: Serialize>(
mut self,
name: String,
argument: T,
) -> Result<Self, serde_json::Error> {
let argument = serde_json::to_value(argument)?;
match &mut self.params {
Params::ByPosition(params) => params.push(argument),
Params::ByName(params) => {
params.insert(name, argument);
}
};
Ok(self)
}
pub fn serialize(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(&self)
}
}
impl Serialize for Request {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let fields_cnt = match &self.params {
Params::ByName(m) if m.is_empty() => 3,
_ => 4,
};
let mut s = s.serialize_struct("Request", fields_cnt)?;
s.serialize_field("id", &self.id)?;
s.serialize_field("jsonrpc", &self.jsonrpc)?;
s.serialize_field("method", &self.method)?;
if fields_cnt == 4 {
s.serialize_field("params", &self.params)?;
}
s.end()
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Response<P> {
pub id: Id,
pub jsonrpc: Option<Version>,
#[serde(flatten)]
pub payload: ResponsePayload<P>,
}
impl<P> Response<P> {
pub fn new_v1_result(id: Id, result: P) -> Self {
Self {
id,
jsonrpc: Some(Version::V1),
payload: ResponsePayload {
result: Some(result),
error: None,
},
}
}
pub fn new_v2_result(id: Id, result: P) -> Self {
Self {
id,
jsonrpc: Some(Version::V2),
payload: ResponsePayload {
result: Some(result),
error: None,
},
}
}
pub fn new_v1_error(id: Id, error: JsonRpcError) -> Self {
Self {
id,
jsonrpc: Some(Version::V1),
payload: ResponsePayload {
result: None,
error: Some(error),
},
}
}
pub fn new_v2_error(id: Id, error: JsonRpcError) -> Self {
Self {
id,
jsonrpc: Some(Version::V2),
payload: ResponsePayload {
result: None,
error: Some(error),
},
}
}
}
#[doc(hidden)]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")]
pub struct ResponsePayload<P> {
result: Option<P>,
error: Option<JsonRpcError>,
}
impl<P> From<ResponsePayload<P>> for Result<P, JsonRpcError> {
fn from(value: ResponsePayload<P>) -> Self {
match value {
ResponsePayload {
error: Some(error),
result: None,
} => Err(error),
ResponsePayload {
error: None,
result: Some(ok),
} => Ok(ok),
ResponsePayload {
error: Some(_),
result: Some(_),
} => Err(JsonRpcError {
code: -32603,
message: "invalid JSON-RPC response, got both `result` and `error`".to_string(),
data: None,
}),
ResponsePayload {
error: None,
result: None,
} => Err(JsonRpcError {
code: -32603,
message: "invalid JSON-RPC response, got neither `result` nor `error`".to_string(),
data: None,
}),
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct JsonRpcError {
pub code: i64,
pub message: String,
#[serde(default)]
pub data: Option<Value>,
}
impl fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"JSON-RPC request failed with code {}: {}",
self.code, self.message
)
}
}
impl StdError for JsonRpcError {}
#[derive(Debug)]
pub enum Error<C> {
Client(C),
JsonRpc(JsonRpcError),
Serde(serde_json::Error),
}
impl<C> fmt::Display for Error<C>
where
C: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Client(inner) => fmt::Display::fmt(inner, f),
Error::JsonRpc(inner) => fmt::Display::fmt(inner, f),
Error::Serde(inner) => fmt::Display::fmt(inner, f),
}
}
}
impl<C> From<serde_json::Error> for Error<C> {
fn from(serde_error: serde_json::Error) -> Self {
Error::Serde(serde_error)
}
}
impl<C> From<JsonRpcError> for Error<C> {
fn from(jsonrpc_error: JsonRpcError) -> Self {
Error::JsonRpc(jsonrpc_error)
}
}
impl<C> StdError for Error<C>
where
C: StdError + 'static,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::Client(inner) => Some(inner),
Error::JsonRpc(inner) => Some(inner),
Error::Serde(inner) => Some(inner),
}
}
}
#[async_trait::async_trait]
pub trait SendRequest: 'static
where
Error<Self::Error>: From<Self::Error>,
{
type Error: StdError;
async fn send_request<P>(
&self,
endpoint: Url,
body: String,
) -> Result<Response<P>, Self::Error>
where
P: DeserializeOwned;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_v1_error_response_error_first_result_second() {
let json = r#"{"error":{"code":-6,"message":"Insufficient funds"},"result":null,"id":0}"#;
let response = serde_json::from_str::<Response<String>>(json).unwrap();
assert_eq!(response.id, Id::Number(0));
assert_eq!(response.jsonrpc, None);
assert_eq!(
Result::from(response.payload),
Err(JsonRpcError {
code: -6,
message: "Insufficient funds".to_owned(),
data: None,
})
)
}
#[test]
fn deserialize_v1_error_response_result_first_error_second() {
let json = r#"{"result":null,"error":{"code":-6,"message":"Insufficient funds"},"id":0}"#;
let response = serde_json::from_str::<Response<String>>(json).unwrap();
assert_eq!(response.id, Id::Number(0));
assert_eq!(response.jsonrpc, None);
assert_eq!(
Result::from(response.payload),
Err(JsonRpcError {
code: -6,
message: "Insufficient funds".to_owned(),
data: None,
})
)
}
#[test]
fn deserialize_v1_error_response_result_first_error_second_unit_type() {
let json = r#"{"result":null,"error":{"code":-6,"message":"Insufficient funds"},"id":0}"#;
let response = serde_json::from_str::<Response<()>>(json).unwrap();
assert_eq!(response.id, Id::Number(0));
assert_eq!(response.jsonrpc, None);
assert_eq!(
Result::from(response.payload),
Err(JsonRpcError {
code: -6,
message: "Insufficient funds".to_owned(),
data: None,
})
)
}
#[test]
fn deserialize_v1_error_no_result() {
let json = r#"{"error":{"code":-6,"message":"Insufficient funds"},"id":0}"#;
let response = serde_json::from_str::<Response<String>>(json).unwrap();
assert_eq!(response.id, Id::Number(0));
assert_eq!(response.jsonrpc, None);
assert_eq!(
Result::from(response.payload),
Err(JsonRpcError {
code: -6,
message: "Insufficient funds".to_owned(),
data: None,
})
)
}
#[test]
fn deserialize_v2_error_response() {
let json = r#"{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}"#;
let response = serde_json::from_str::<Response<()>>(json).unwrap();
assert_eq!(response.id, Id::String("1".to_owned()));
assert_eq!(response.jsonrpc, Some(Version::V2));
assert_eq!(
Result::from(response.payload),
Err(JsonRpcError {
code: -32601,
message: "Method not found".to_owned(),
data: None,
})
)
}
#[test]
fn deserialize_error_response_with_data() {
let json = r#"{"jsonrpc": "2.0", "error": {"code": 1010, "message": "Invalid Transaction", "data": "BadProof"}, "id": "1"}"#;
let response = serde_json::from_str::<Response<()>>(json).unwrap();
assert_eq!(response.id, Id::String("1".to_owned()));
assert_eq!(response.jsonrpc, Some(Version::V2));
assert_eq!(
Result::from(response.payload),
Err(JsonRpcError {
code: 1010,
message: "Invalid Transaction".to_owned(),
data: Some(Value::String("BadProof".to_owned())),
})
)
}
#[test]
fn deserialize_success_response() {
let json = r#"{"jsonrpc": "2.0", "result": 19, "id": 1}"#;
let response = serde_json::from_str::<Response<i32>>(json).unwrap();
assert_eq!(response.id, Id::Number(1));
assert_eq!(response.jsonrpc, Some(Version::V2));
assert_eq!(Result::from(response.payload), Ok(19))
}
#[test]
fn serialize_request_v1() {
let request = Request::new_v1("subtract")
.with_argument("first".to_owned(), 42)
.unwrap()
.with_argument("second".to_owned(), 23)
.unwrap();
let json = request.serialize().unwrap();
assert_eq!(
json,
r#"{"id":0,"jsonrpc":"1.0","method":"subtract","params":[42,23]}"#
);
}
#[test]
fn serialize_request_v2() {
let request = Request::new_v2("subtract")
.with_argument("first".to_owned(), 42)
.unwrap()
.with_argument("second".to_owned(), 23)
.unwrap();
let json = request.serialize().unwrap();
assert_eq!(
json,
r#"{"id":0,"jsonrpc":"2.0","method":"subtract","params":{"first":42,"second":23}}"#
);
}
#[test]
fn serialize_request_v2_empty_params() {
let request = Request::new_v2("subtract");
let json = request.serialize().unwrap();
assert_eq!(json, r#"{"id":0,"jsonrpc":"2.0","method":"subtract"}"#);
}
}