Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce allocations #8

Merged
merged 1 commit into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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