Skip to content

Commit

Permalink
Reduce allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
Rexagon authored and 0xdeafbeef committed Feb 15, 2023
1 parent 1ef79d4 commit 073136f
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 30 deletions.
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
pub const PARSE_ERROR: i32 = -32700;

#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub enum JsonRpcErrorReason {
ParseError,
InvalidRequest,
Expand Down Expand Up @@ -66,7 +66,7 @@ impl JsonRpcErrorReason {
}
}

#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Error, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct JsonRpcError {
code: i32,
message: String,
Expand Down
142 changes: 114 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#![deny(unreachable_pub, private_in_public)]
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]

use std::borrow::Cow;

use axum::body::HttpBody;
use axum::extract::FromRequest;
use axum::http::Request;
Expand All @@ -51,15 +53,65 @@ pub mod error;
/// Hack until [try_trait_v2](https://github.com/rust-lang/rust/issues/84277) is not stabilized
pub type JrpcResult = Result<JsonRpcResponse, JsonRpcResponse>;

#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
#[derive(Debug)]
pub struct JsonRpcRequest {
pub id: i64,
pub jsonrpc: String,
pub method: String,
pub params: Value,
}

impl Serialize for JsonRpcRequest {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct Helper<'a> {
jsonrpc: &'static str,
id: i64,
method: &'a str,
params: &'a Value,
}

Helper {
jsonrpc: JSONRPC,
id: self.id,
method: &self.method,
params: &self.params,
}
.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for JsonRpcRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;

#[derive(Deserialize)]
struct Helper<'a> {
#[serde(borrow)]
jsonrpc: Cow<'a, str>,
id: i64,
method: String,
params: Value,
}

let helper = Helper::deserialize(deserializer)?;
if helper.jsonrpc == JSONRPC {
Ok(Self {
id: helper.id,
method: helper.method,
params: helper.params,
})
} else {
Err(D::Error::custom("Unknown jsonrpc version"))
}
}
}

#[derive(Debug)]
/// Parses a JSON-RPC request, and returns the request ID, the method name, and the parameters.
/// If the request is invalid, returns an error.
Expand Down Expand Up @@ -136,7 +188,6 @@ where
Err(e) => {
return Err(JsonRpcResponse {
id: 0,
jsonrpc: "2.0".to_owned(),
result: JsonRpcAnswer::Error(JsonRpcError::new(
JsonRpcErrorReason::InvalidRequest,
e.to_string(),
Expand All @@ -145,17 +196,7 @@ where
})
}
};
if parsed.jsonrpc != "2.0" {
return Err(JsonRpcResponse {
id: parsed.id,
jsonrpc: "2.0".to_owned(),
result: JsonRpcAnswer::Error(JsonRpcError::new(
JsonRpcErrorReason::InvalidRequest,
"Invalid jsonrpc version".to_owned(),
Value::Null,
)),
});
}

Ok(Self {
parsed: parsed.params,
method: parsed.method,
Expand All @@ -164,24 +205,20 @@ where
}
}

#[derive(Serialize, Debug, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
/// A JSON-RPC response.
pub struct JsonRpcResponse {
jsonrpc: String,
#[serde(flatten)]
/// Request content.
pub result: JsonRpcAnswer,
/// The request ID.
id: i64,
pub id: i64,
}

impl JsonRpcResponse {
fn new(id: i64, result: JsonRpcAnswer) -> Self {
Self {
jsonrpc: "2.0".to_owned(),
result,
id,
}
Self { result, id }
}

/// Returns a response with the given result
/// Returns JsonRpcError if the `result` is invalid input for [`serde_json::to_value`]
pub fn success<T: Serialize>(id: i64, result: T) -> Self {
Expand All @@ -202,27 +239,78 @@ impl JsonRpcResponse {

pub fn error(id: i64, error: JsonRpcError) -> Self {
JsonRpcResponse {
jsonrpc: "2.0".to_owned(),
result: JsonRpcAnswer::Error(error),
id,
}
}
}

impl Serialize for JsonRpcResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct Helper<'a> {
jsonrpc: &'static str,
#[serde(flatten)]
result: &'a JsonRpcAnswer,
id: i64,
}

Helper {
jsonrpc: JSONRPC,
result: &self.result,
id: self.id,
}
.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for JsonRpcResponse {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;

#[derive(Deserialize)]
struct Helper<'a> {
#[serde(borrow)]
jsonrpc: Cow<'a, str>,
#[serde(flatten)]
result: JsonRpcAnswer,
id: i64,
}

let helper = Helper::deserialize(deserializer)?;
if helper.jsonrpc == JSONRPC {
Ok(Self {
result: helper.result,
id: helper.id,
})
} else {
Err(D::Error::custom("Unknown jsonrpc version"))
}
}
}

impl IntoResponse for JsonRpcResponse {
fn into_response(self) -> Response {
Json(self).into_response()
}
}

#[derive(Serialize, Debug, Deserialize, PartialEq, Eq)]
#[derive(Serialize, Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
/// JsonRpc [response object](https://www.jsonrpc.org/specification#response_object)
pub enum JsonRpcAnswer {
Result(Value),
Error(JsonRpcError),
}

const JSONRPC: &str = "2.0";

#[cfg(test)]
#[cfg(feature = "anyhow_error")]
mod test {
Expand Down Expand Up @@ -250,7 +338,6 @@ mod test {
.post("/")
.json(&JsonRpcRequest {
id: 0,
jsonrpc: "2.0".to_owned(),
method: "add".to_owned(),
params: serde_json::to_value(Test { a: 0, b: 111 }).unwrap(),
})
Expand All @@ -264,7 +351,6 @@ mod test {
.post("/")
.json(&JsonRpcRequest {
id: 0,
jsonrpc: "2.0".to_owned(),
method: "lol".to_owned(),
params: serde_json::to_value(()).unwrap(),
})
Expand Down

0 comments on commit 073136f

Please sign in to comment.