-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow trait bounds to be overridden in macro (#808)
* Parse user defined client_bounds and server_bounds Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Use custom user defined bounds if provided Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Add provided where clause to the custom bounds Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Add proc_macro with bounds example Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Check against client_bounds wihtout client implementation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * tests: Add ui test for empty bounds Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * tests: Add ui test to check bounds without implementation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Add bounds documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc_macro: Remove `WherePredicate` from parsing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * ui: Add test that compiles Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Rename rendered `T` to avoid collision with user provided generic Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * tests: Modify UI correct rpc_bounds test to call server's methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
- Loading branch information
Showing
11 changed files
with
295 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright 2019-2021 Parity Technologies (UK) Ltd. | ||
// | ||
// Permission is hereby granted, free of charge, to any | ||
// person obtaining a copy of this software and associated | ||
// documentation files (the "Software"), to deal in the | ||
// Software without restriction, including without | ||
// limitation the rights to use, copy, modify, merge, | ||
// publish, distribute, sublicense, and/or sell copies of | ||
// the Software, and to permit persons to whom the Software | ||
// is furnished to do so, subject to the following | ||
// conditions: | ||
// | ||
// The above copyright notice and this permission notice | ||
// shall be included in all copies or substantial portions | ||
// of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | ||
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR | ||
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
// DEALINGS IN THE SOFTWARE. | ||
|
||
use std::net::SocketAddr; | ||
|
||
use jsonrpsee::core::{async_trait, Error}; | ||
use jsonrpsee::proc_macros::rpc; | ||
use jsonrpsee::ws_client::WsClientBuilder; | ||
use jsonrpsee::ws_server::{WsServerBuilder, WsServerHandle}; | ||
|
||
type ExampleHash = [u8; 32]; | ||
|
||
pub trait Config { | ||
type Hash: Send + Sync + 'static; | ||
} | ||
|
||
impl Config for ExampleHash { | ||
type Hash = Self; | ||
} | ||
|
||
/// The RPC macro requires `DeserializeOwned` for output types for the client implementation, while the | ||
/// server implementation requires output types to be bounded by `Serialize`. | ||
/// | ||
/// In this example, we don't want the `Conf` to be bounded by default to | ||
/// `Conf : Send + Sync + 'static + jsonrpsee::core::DeserializeOwned` for client implementation and | ||
/// `Conf : Send + Sync + 'static + jsonrpsee::core::Serialize` for server implementation. | ||
/// | ||
/// Explicitly, specify client and server bounds to handle the `Serialize` and `DeserializeOwned` cases | ||
/// just for the `Conf::hash` part. | ||
#[rpc(server, client, namespace = "foo", client_bounds(T::Hash: jsonrpsee::core::DeserializeOwned), server_bounds(T::Hash: jsonrpsee::core::Serialize))] | ||
pub trait Rpc<T: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> Result<T::Hash, Error>; | ||
} | ||
|
||
pub struct RpcServerImpl; | ||
|
||
#[async_trait] | ||
impl RpcServer<ExampleHash> for RpcServerImpl { | ||
fn method(&self) -> Result<<ExampleHash as Config>::Hash, Error> { | ||
Ok([0u8; 32]) | ||
} | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
tracing_subscriber::FmtSubscriber::builder() | ||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) | ||
.try_init() | ||
.expect("setting default subscriber failed"); | ||
|
||
let (server_addr, _handle) = run_server().await?; | ||
let url = format!("ws://{}", server_addr); | ||
|
||
let client = WsClientBuilder::default().build(&url).await?; | ||
assert_eq!(RpcClient::<ExampleHash>::method(&client).await.unwrap(), [0u8; 32]); | ||
|
||
Ok(()) | ||
} | ||
|
||
async fn run_server() -> anyhow::Result<(SocketAddr, WsServerHandle)> { | ||
let server = WsServerBuilder::default().build("127.0.0.1:0").await?; | ||
|
||
let addr = server.local_addr()?; | ||
let handle = server.start(RpcServerImpl.into_rpc())?; | ||
Ok((addr, handle)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
//! Example of using proc macro to generate working client and server with bounds applied. | ||
|
||
use std::net::SocketAddr; | ||
|
||
use jsonrpsee::core::{async_trait, RpcResult}; | ||
use jsonrpsee::proc_macros::rpc; | ||
use jsonrpsee::ws_client::*; | ||
use jsonrpsee::ws_server::WsServerBuilder; | ||
|
||
pub trait Config { | ||
type Hash: Send + Sync + 'static; | ||
type NotUsed; | ||
} | ||
|
||
type ExampleHash = [u8; 32]; | ||
impl Config for ExampleHash { | ||
type Hash = Self; | ||
type NotUsed = (); | ||
} | ||
|
||
/// Client only RPC. | ||
#[rpc(client, namespace = "foo", client_bounds(Conf::Hash: jsonrpsee::core::DeserializeOwned))] | ||
pub trait MyRpcC<Conf: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> RpcResult<Conf::Hash>; | ||
} | ||
|
||
/// Server only RPC. | ||
#[rpc(server, namespace = "foo", server_bounds(Conf::Hash: jsonrpsee::core::Serialize))] | ||
pub trait MyRpcS<Conf: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> RpcResult<Conf::Hash>; | ||
} | ||
|
||
/// Client and server RPC. | ||
#[rpc(server, client, namespace = "foo", client_bounds(Conf::Hash: jsonrpsee::core::DeserializeOwned), server_bounds(Conf::Hash: jsonrpsee::core::Serialize))] | ||
pub trait MyRpcSC<Conf: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> RpcResult<Conf::Hash>; | ||
} | ||
|
||
/// Implementation for the `MyRpcS` trait (server only). | ||
pub struct ServerOnlyImpl; | ||
#[async_trait] | ||
impl MyRpcSServer<ExampleHash> for ServerOnlyImpl { | ||
fn method(&self) -> RpcResult<<ExampleHash as Config>::Hash> { | ||
Ok([0u8; 32]) | ||
} | ||
} | ||
|
||
/// Implementation for the `MyRpcSC` trait (client server rpc). | ||
pub struct ServerClientServerImpl; | ||
#[async_trait] | ||
impl MyRpcSCServer<ExampleHash> for ServerClientServerImpl { | ||
fn method(&self) -> RpcResult<<ExampleHash as Config>::Hash> { | ||
Ok([0u8; 32]) | ||
} | ||
} | ||
|
||
pub async fn websocket_servers() -> (SocketAddr, SocketAddr) { | ||
// Start server from `MyRpcS` trait. | ||
let server = WsServerBuilder::default().build("127.0.0.1:0").await.unwrap(); | ||
let addr_server_only = server.local_addr().unwrap(); | ||
server.start(ServerOnlyImpl.into_rpc()).unwrap(); | ||
|
||
// Start server from `MyRpcSC` trait. | ||
let server = WsServerBuilder::default().build("127.0.0.1:0").await.unwrap(); | ||
let addr_server_client = server.local_addr().unwrap(); | ||
server.start(ServerClientServerImpl.into_rpc()).unwrap(); | ||
|
||
(addr_server_only, addr_server_client) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let (server_addr, server_addr_w_client) = websocket_servers().await; | ||
let (server_url, server_w_client_url) = (format!("ws://{}", server_addr), format!("ws://{}", server_addr_w_client)); | ||
let client = WsClientBuilder::default().build(&server_url).await.unwrap(); | ||
let client_second = WsClientBuilder::default().build(&server_w_client_url).await.unwrap(); | ||
|
||
// Use `MyRpcC` client to communicate to the `MyRpcS` server. | ||
assert_eq!(MyRpcCClient::<ExampleHash>::method(&client).await.unwrap(), [0u8; 32]); | ||
// Use `MyRpcC` client to communicate to the `MyRpcSC` server. | ||
assert_eq!(MyRpcCClient::<ExampleHash>::method(&client_second).await.unwrap(), [0u8; 32]); | ||
|
||
// Use `MyRpcSC` client to communicate to the `MyRpcS` server. | ||
assert_eq!(MyRpcCClient::<ExampleHash>::method(&client).await.unwrap(), [0u8; 32]); | ||
// Use `MyRpcSC` client to communicate to the `MyRpcSC` server. | ||
assert_eq!(MyRpcSCClient::<ExampleHash>::method(&client_second).await.unwrap(), [0u8; 32]); | ||
} |
19 changes: 19 additions & 0 deletions
19
proc-macros/tests/ui/incorrect/rpc/rpc_bounds_without_impl.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use jsonrpsee::proc_macros::rpc; | ||
|
||
pub trait Config { | ||
type Hash: Send + Sync + 'static; | ||
} | ||
|
||
#[rpc(server, client_bounds(), server_bounds(Conf::Hash: jsonrpsee::core::Serialize))] | ||
pub trait ClientBoundsForbidden<Conf: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> Result<Conf::Hash, Error>; | ||
} | ||
|
||
#[rpc(client, server_bounds(), client_bounds(Conf::Hash: jsonrpsee::core::DeserializeOwned))] | ||
pub trait ServerBoundsForbidden<Conf: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> Result<Conf::Hash, Error>; | ||
} | ||
|
||
fn main() {} |
11 changes: 11 additions & 0 deletions
11
proc-macros/tests/ui/incorrect/rpc/rpc_bounds_without_impl.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
error: Attribute 'client' must be specified with 'client_bounds' | ||
--> tests/ui/incorrect/rpc/rpc_bounds_without_impl.rs:8:11 | ||
| | ||
8 | pub trait ClientBoundsForbidden<Conf: Config> { | ||
| ^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: Attribute 'server' must be specified with 'server_bounds' | ||
--> tests/ui/incorrect/rpc/rpc_bounds_without_impl.rs:14:11 | ||
| | ||
14 | pub trait ServerBoundsForbidden<Conf: Config> { | ||
| ^^^^^^^^^^^^^^^^^^^^^ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
use jsonrpsee::proc_macros::rpc; | ||
use jsonrpsee::core::Error; | ||
|
||
pub trait Config { | ||
type Hash: Send + Sync + 'static; | ||
} | ||
|
||
/// Client bound must be `Conf::Hash: jsonrpsee::core::DeserializeOwned` | ||
/// Server bound must be `Conf::Hash: jsonrpsee::core::Serialize` | ||
#[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] | ||
pub trait EmptyBounds<Conf: Config> { | ||
#[method(name = "bar")] | ||
fn method(&self) -> Result<Conf::Hash, Error>; | ||
} | ||
|
||
fn main() {} |
26 changes: 26 additions & 0 deletions
26
proc-macros/tests/ui/incorrect/rpc/rpc_empty_bounds.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
error[E0277]: the trait bound `<Conf as Config>::Hash: Serialize` is not satisfied | ||
--> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:10:1 | ||
| | ||
10 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serialize` is not implemented for `<Conf as Config>::Hash` | ||
| | ||
note: required by a bound in `RpcModule::<Context>::register_method` | ||
--> $WORKSPACE/core/src/server/rpc_module.rs | ||
| | ||
| R: Serialize, | ||
| ^^^^^^^^^ 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 | ||
--> tests/ui/incorrect/rpc/rpc_empty_bounds.rs:10:1 | ||
| | ||
10 | #[rpc(server, client, namespace = "foo", client_bounds(), server_bounds())] | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'de> Deserialize<'de>` is not implemented for `<Conf as Config>::Hash` | ||
| | ||
= note: required because of the requirements on the impl of `DeserializeOwned` for `<Conf as Config>::Hash` | ||
note: required by a bound in `request` | ||
--> $WORKSPACE/core/src/client/mod.rs | ||
| | ||
| R: DeserializeOwned; | ||
| ^^^^^^^^^^^^^^^^ required by this bound in `request` | ||
= note: this error originates in the attribute macro `rpc` (in Nightly builds, run with -Z macro-backtrace for more info) |