paritytech / jsonrpsee

Rust JSON-RPC library on top of async/await
MIT License
643 stars 173 forks source link

rpc attribute fails with `client, server` argument: `context` is a reference that is only valid in the closure body #1459

Closed AArnott closed 1 month ago

AArnott commented 1 month ago

I have client and server scenarios working independently. But when a trait defines a contract for which I need both client and server, I get this error on compilation:

context is a reference that is only valid in the closure body

It happens on the rpc attribute of this snippet:

#[rpc(client, server)]
trait Calculator {
    #[method(name = "add")]
    fn add(&self, a: u64, b: u64) -> Result<u64, ErrorObject>;
}

How do I resolve this?

Full repro

Cargo.toml ```toml [package] name = "jsonrpctest" version = "0.1.0" edition = "2021" [dependencies] async-trait = "0.1.82" jsonrpsee = { version = "0.24", features = ["macros", "async-client", "server-core"] } serde = "1" tokio = { version = "1.40", features = ["macros", "rt-multi-thread", "io-util"] } ```
main.rs ```rs use async_trait::async_trait; use jsonrpsee::{ async_client::{Client, ClientBuilder}, core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}, proc_macros::rpc, types::ErrorObject, }; use tokio::io::{ duplex, split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, DuplexStream, ReadHalf, WriteHalf, }; #[tokio::main] async fn main() { let (client_stream, server_stream) = duplex(1024); tokio::spawn(mock_server(server_stream)); let client = build_client(client_stream); let sum = client.add(3, 5).await.unwrap(); println!("{}", sum); } async fn mock_server(mut pipe: DuplexStream) -> Result<(), std::io::Error> { let mut buffer = vec![0u8; 1024]; let read = pipe.read(&mut buffer).await?; println!( "Received JSON request:\n{}", String::from_utf8_lossy(&buffer[..read]) ); // Send our mock response let response = r#" { "jsonrpc": "2.0", "id": 0, "result": 8 } "#; pipe.write_all(response.as_bytes()).await?; pipe.flush().await?; Ok(()) } fn duplex_transport( duplex: T, ) -> ( AsyncReadTransport>, AsyncWriteTransport>, ) { let (reader, writer) = split(duplex); let reader = AsyncReadTransport { reader }; let writer = AsyncWriteTransport { writer }; (reader, writer) } fn build_client(duplex: T) -> Client { let (r, w) = duplex_transport(duplex); ClientBuilder::default().build_with_tokio(w, r) } #[rpc(client, server)] // ERROR `context` is a reference that is only valid in the closure body trait Calculator { #[method(name = "add")] fn add(&self, a: u64, b: u64) -> Result; } struct AsyncWriteTransport { writer: T, } #[async_trait] impl TransportSenderT for AsyncWriteTransport { type Error = std::io::Error; async fn send(&mut self, msg: String) -> Result<(), Self::Error> { self.writer.write_all(msg.as_bytes()).await?; Ok(()) } } struct AsyncReadTransport { reader: T, } #[async_trait] impl TransportReceiverT for AsyncReadTransport { type Error = std::io::Error; async fn receive(&mut self) -> Result { let mut buffer = vec![0u8; 1024]; let read = self.reader.read(&mut buffer[..]).await?; Ok(ReceivedMessage::Bytes(buffer[..read].to_vec())) } } ```
AArnott commented 1 month ago

It looks like the bug is that ErrorObject is not a valid error type. This fixed it:

-    fn add(&self, a: u64, b: u64) -> Result<u64, ErrorObject>;
+    fn add(&self, a: u64, b: u64) -> Result<u64, ErrorObjectOwned>;