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

fix(proc macros): support parsing params !Result #1094

Merged
merged 2 commits into from
Apr 24, 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
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
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