Skip to content

Commit

Permalink
fi(proc macros): support parsing param !Result
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasad1 committed Apr 24, 2023
1 parent d6a9622 commit 051e247
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 37 deletions.
19 changes: 19 additions & 0 deletions core/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ where
}
}

impl<T> IntoResponse for ResponsePayload<'static, T>
where
T: serde::Serialize + Clone,
{
type Output = T;

fn into_response(self) -> ResponsePayload<'static, Self::Output> {
self
}
}

impl IntoResponse for ErrorObjectOwned {
type Output = ErrorObjectOwned;

fn into_response(self) -> ResponsePayload<'static, Self::Output> {
ResponsePayload::Error(self)
}
}

macro_rules! impl_into_response {
($($n:ty),*) => {
$(
Expand Down
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tracing = "0.1.34"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
tokio = { version = "1.16", features = ["full"] }
tokio-stream = { version = "0.1", features = ["sync"] }
serde = "1"
serde_json = { version = "1" }
tower-http = { version = "0.4.0", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
Expand Down
30 changes: 18 additions & 12 deletions proc-macros/src/render_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,15 @@ impl RpcDescription {
// called..
let (parsing, params_seq) = self.render_params_decoding(&method.params, None);

let into_response = self.jrps_server_item(quote! { IntoResponse });

check_name(&rpc_method_name, rust_method_name.span());

if method.signature.sig.asyncness.is_some() {
handle_register_result(quote! {
rpc.register_async_method(#rpc_method_name, |params, context| async move {
#parsing
context.as_ref().#rust_method_name(#params_seq).await
#into_response::into_response(context.as_ref().#rust_method_name(#params_seq).await)
})
})
} else {
Expand All @@ -145,7 +147,7 @@ impl RpcDescription {
handle_register_result(quote! {
rpc.#register_kind(#rpc_method_name, |params, context| {
#parsing
context.#rust_method_name(#params_seq)
#into_response::into_response(context.#rust_method_name(#params_seq))
})
})
}
Expand Down Expand Up @@ -286,7 +288,7 @@ impl RpcDescription {
let params_fields = quote! { #(#params_fields_seq),* };
let tracing = self.jrps_server_item(quote! { tracing });
let sub_err = self.jrps_server_item(quote! { SubscriptionCloseResponse });
let err_obj = self.jrps_server_item(quote! { types::ErrorObjectOwned });
let response_payload = self.jrps_server_item(quote! { types::ResponsePayload });

// Code to decode sequence of parameters from a JSON array.
let decode_array = {
Expand All @@ -297,7 +299,7 @@ impl RpcDescription {
Ok(v) => v,
Err(e) => {
#tracing::warn!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e);
#pending.reject(#err_obj::from(e)).await;
#pending.reject(e).await;
return #sub_err::None;
}
};
Expand All @@ -309,7 +311,7 @@ impl RpcDescription {
Ok(v) => v,
Err(e) => {
#tracing::warn!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e);
return Err(e.into())
return #response_payload::Error(e);
}
};
}
Expand All @@ -320,7 +322,7 @@ impl RpcDescription {
Ok(v) => v,
Err(e) => {
#tracing::warn!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e);
#pending.reject(#err_obj::from(e)).await;
#pending.reject(e).await;
return #sub_err::None;
}
};
Expand All @@ -332,7 +334,7 @@ impl RpcDescription {
Ok(v) => v,
Err(e) => {
#tracing::warn!(concat!("Error parsing \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e);
return Err(e.into())
return #response_payload::Error(e);
}
};
}
Expand Down Expand Up @@ -398,7 +400,7 @@ impl RpcDescription {
Ok(p) => p,
Err(e) => {
#tracing::warn!("Failed to parse JSON-RPC params as object: {}", e);
#pending.reject(#err_obj::from(e)).await;
#pending.reject(e).await;
return #sub_err::None;
}
};
Expand All @@ -413,10 +415,14 @@ impl RpcDescription {
#(#fields)*
}

let parsed: ParamsObject<#(#types,)*> = params.parse().map_err(|e| {
#tracing::warn!("Failed to parse JSON-RPC params as object: {}", e);
e
})?;
let parsed: ParamsObject<#(#types,)*> = match params.parse() {
Ok(p) => p,
Err(e) => {
#tracing::warn!("Failed to parse JSON-RPC params as object: {}", e);
return #response_payload::Error(e);
}
};

(#(#destruct),*)
}
}
Expand Down
93 changes: 79 additions & 14 deletions proc-macros/tests/ui/correct/custom_ret_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::net::SocketAddr;

use jsonrpsee::core::{async_trait, Serialize};
use jsonrpsee::core::{async_trait, Error, Serialize};
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::{IntoResponse, ServerBuilder};
use jsonrpsee::types::ResponsePayload;
Expand Down Expand Up @@ -37,23 +37,51 @@ impl IntoResponse for CustomError {

#[rpc(server, namespace = "foo")]
pub trait Rpc {
#[method(name = "method1")]
async fn method1(&self) -> CustomError;
#[method(name = "async_method1")]
async fn async_method1(&self) -> CustomError;

#[method(name = "method2")]
async fn method2(&self) -> CustomError;
#[method(name = "async_method2")]
async fn async_method2(&self, x: u32) -> CustomError;

#[method(name = "sync_method1")]
fn method1(&self) -> CustomError;

#[method(name = "sync_method2")]
fn method2(&self, x: u32) -> CustomError;

#[method(name = "blocking_method1", blocking)]
fn blocking_method1(&self) -> CustomError;

#[method(name = "blocking_method2", blocking)]
fn blocking_method2(&self, x: u32) -> CustomError;
}

pub struct RpcServerImpl;

#[async_trait]
impl RpcServer for RpcServerImpl {
async fn method1(&self) -> CustomError {
async fn async_method1(&self) -> CustomError {
CustomError::One
}

async fn async_method2(&self, x: u32) -> CustomError {
CustomError::Two { custom_data: x }
}

fn method1(&self) -> CustomError {
CustomError::One
}

fn method2(&self, x: u32) -> CustomError {
CustomError::Two { custom_data: x }
}

fn blocking_method1(&self) -> CustomError {
CustomError::One
}

async fn method2(&self) -> CustomError {
CustomError::Two { custom_data: 123 }
fn blocking_method2(&self, x: u32) -> CustomError {
CustomError::Two { custom_data: x }
}
}

Expand All @@ -62,11 +90,23 @@ impl RpcServer for RpcServerImpl {
// The client accepts only return types that are `Result<T, E>`.
#[rpc(client, namespace = "foo")]
pub trait RpcClient {
#[method(name = "method1")]
async fn client_method1(&self) -> RpcResult<serde_json::Value>;
#[method(name = "async_method1")]
async fn async_method1(&self) -> RpcResult<serde_json::Value>;

#[method(name = "async_method2")]
async fn async_method2(&self, x: u32) -> Result<serde_json::Value, ()>;

#[method(name = "sync_method1")]
async fn sync_method1(&self) -> RpcResult<serde_json::Value>;

#[method(name = "method2")]
async fn client_method2(&self) -> Result<serde_json::Value, ()>;
#[method(name = "sync_method2")]
async fn sync_method2(&self, x: u32) -> Result<serde_json::Value, ()>;

#[method(name = "blocking_method1")]
async fn blocking_method1(&self) -> RpcResult<serde_json::Value>;

#[method(name = "blocking_method2")]
async fn blocking_method2(&self, x: u32) -> Result<serde_json::Value, ()>;
}

pub async fn server() -> SocketAddr {
Expand All @@ -85,18 +125,43 @@ async fn main() {
let server_url = format!("ws://{}", server_addr);
let client = WsClientBuilder::default().build(&server_url).await.unwrap();

let error = client.async_method1().await.unwrap_err();
assert_method1(error);

let error = client.async_method2(123).await.unwrap_err();
assert_method2(error);

let error = client.sync_method1().await.unwrap_err();
assert_method1(error);

let error = client.sync_method2(123).await.unwrap_err();
assert_method2(error);

let error = client.blocking_method1().await.unwrap_err();
assert_method1(error);

let error = client.blocking_method2(123).await.unwrap_err();
assert_method2(error);
}

fn assert_method1(error: Error) {
let get_error_object = |err| match err {
jsonrpsee::core::Error::Call(object) => object,
_ => panic!("wrong error kind: {:?}", err),
};

let error = client.client_method1().await.unwrap_err();
let error_object = get_error_object(error);
assert_eq!(error_object.code(), 101);
assert_eq!(error_object.message(), "custom_error");
assert!(error_object.data().is_none());
}

fn assert_method2(error: Error) {
let get_error_object = |err| match err {
jsonrpsee::core::Error::Call(object) => object,
_ => panic!("wrong error kind: {:?}", err),
};

let error = client.client_method2().await.unwrap_err();
let error_object = get_error_object(error);
assert_eq!(error_object.code(), 102);
assert_eq!(error_object.message(), "custom_error");
Expand Down
10 changes: 0 additions & 10 deletions proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ error[E0277]: the trait bound `<Conf as Config>::Hash: Serialize` is not satisfi
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `<Conf as Config>::Hash`
|
= note: required for `std::result::Result<<Conf as Config>::Hash, ErrorObject<'_>>` to implement `IntoResponse`
note: required by a bound in `RpcModule::<Context>::register_method`
--> $WORKSPACE/core/src/server/rpc_module.rs
|
| R: IntoResponse + 'static,
| ^^^^^^^^^^^^ required by this bound in `RpcModule::<Context>::register_method`
= note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `<Conf as Config>::Hash: Clone` is not satisfied
Expand All @@ -19,11 +14,6 @@ error[E0277]: the trait bound `<Conf as Config>::Hash: Clone` is not satisfied
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `<Conf as Config>::Hash`
|
= note: required for `std::result::Result<<Conf as Config>::Hash, ErrorObject<'_>>` to implement `IntoResponse`
note: required by a bound in `RpcModule::<Context>::register_method`
--> $WORKSPACE/core/src/server/rpc_module.rs
|
| R: IntoResponse + 'static,
| ^^^^^^^^^^^^ required by this bound in `RpcModule::<Context>::register_method`
= note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `for<'de> <Conf as Config>::Hash: Deserialize<'de>` is not satisfied
Expand Down
32 changes: 31 additions & 1 deletion tests/tests/proc_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ mod rpc_impl {
};
use jsonrpsee::core::{async_trait, SubscriptionResult};
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::types::ErrorObjectOwned;
use jsonrpsee::types::{ErrorObject, ErrorObjectOwned};

pub struct CustomSubscriptionRet;

Expand All @@ -57,6 +57,14 @@ mod rpc_impl {
}
}

pub struct MyError;

impl From<MyError> for ErrorObjectOwned {
fn from(_: MyError) -> Self {
ErrorObject::owned(1, "my_error", None::<()>)
}
}

#[rpc(client, server, namespace = "foo")]
pub trait Rpc {
#[method(name = "foo")]
Expand Down Expand Up @@ -114,6 +122,28 @@ mod rpc_impl {
std::thread::sleep(std::time::Duration::from_millis(50));
Ok(42)
}

#[method(name = "blocking_call_custom_err", blocking)]
fn blocking_call_custom_err(&self) -> Result<usize, MyError> {
std::thread::sleep(std::time::Duration::from_millis(50));
Ok(42)
}

#[method(name = "blocking_call_custom_err_with_params", blocking)]
fn blocking_call_custom_err_with_params(&self, x: usize) -> Result<usize, MyError> {
std::thread::sleep(std::time::Duration::from_millis(50));
Ok(x)
}

#[method(name = "my_err")]
fn custom_error(&self) -> Result<(), MyError> {
Err(MyError)
}

#[method(name = "my_err_with_param")]
fn custom_error_with_param(&self, _s: String) -> Result<(), MyError> {
Err(MyError)
}
}

pub struct RpcServerImpl;
Expand Down

0 comments on commit 051e247

Please sign in to comment.