jsonrpc_derive/lib.rs
1//! High level, typed wrapper for `jsonrpc_core`.
2//!
3//! Enables creation of "Service" objects grouping a set of RPC methods together in a typed manner.
4//!
5//! Example
6//!
7//! ```
8//! use jsonrpc_core::{IoHandler, Result, BoxFuture};
9//! use jsonrpc_core::futures::future;
10//! use jsonrpc_derive::rpc;
11//!
12//! #[rpc(server)]
13//! pub trait Rpc {
14//! #[rpc(name = "protocolVersion")]
15//! fn protocol_version(&self) -> Result<String>;
16//!
17//! #[rpc(name = "add")]
18//! fn add(&self, a: u64, b: u64) -> Result<u64>;
19//!
20//! #[rpc(name = "callAsync")]
21//! fn call(&self, a: u64) -> BoxFuture<Result<String>>;
22//! }
23//!
24//! struct RpcImpl;
25//! impl Rpc for RpcImpl {
26//! fn protocol_version(&self) -> Result<String> {
27//! Ok("version1".into())
28//! }
29//!
30//! fn add(&self, a: u64, b: u64) -> Result<u64> {
31//! Ok(a + b)
32//! }
33//!
34//! fn call(&self, _: u64) -> BoxFuture<Result<String>> {
35//! Box::pin(future::ready(Ok("OK".to_owned()).into()))
36//! }
37//! }
38//!
39//! fn main() {
40//! let mut io = IoHandler::new();
41//! let rpc = RpcImpl;
42//!
43//! io.extend_with(rpc.to_delegate());
44//! }
45//! ```
46//!
47//! Pub/Sub Example
48//!
49//! Each subscription must have `subscribe` and `unsubscribe` methods. They can
50//! have any name but must be annotated with `subscribe` or `unsubscribe` and
51//! have a matching unique subscription name.
52//!
53//! ```
54//! use std::sync::{atomic, Arc, RwLock};
55//! use std::collections::HashMap;
56//!
57//! use jsonrpc_core::{Error, ErrorCode, Result};
58//! use jsonrpc_derive::rpc;
59//! use jsonrpc_pubsub::{Session, PubSubHandler, SubscriptionId, typed::{Subscriber, Sink}};
60//!
61//! #[rpc]
62//! pub trait Rpc {
63//! type Metadata;
64//!
65//! /// Hello subscription
66//! #[pubsub(
67//! subscription = "hello",
68//! subscribe,
69//! name = "hello_subscribe",
70//! alias("hello_sub")
71//! )]
72//! fn subscribe(&self, _: Self::Metadata, _: Subscriber<String>, param: u64);
73//!
74//! /// Unsubscribe from hello subscription.
75//! #[pubsub(
76//! subscription = "hello",
77//! unsubscribe,
78//! name = "hello_unsubscribe"
79//! )]
80//! fn unsubscribe(&self, _: Option<Self::Metadata>, _: SubscriptionId) -> Result<bool>;
81//! }
82//!
83//!
84//! #[derive(Default)]
85//! struct RpcImpl {
86//! uid: atomic::AtomicUsize,
87//! active: Arc<RwLock<HashMap<SubscriptionId, Sink<String>>>>,
88//! }
89//! impl Rpc for RpcImpl {
90//! type Metadata = Arc<Session>;
91//!
92//! fn subscribe(&self, _meta: Self::Metadata, subscriber: Subscriber<String>, param: u64) {
93//! if param != 10 {
94//! subscriber.reject(Error {
95//! code: ErrorCode::InvalidParams,
96//! message: "Rejecting subscription - invalid parameters provided.".into(),
97//! data: None,
98//! }).unwrap();
99//! return;
100//! }
101//!
102//! let id = self.uid.fetch_add(1, atomic::Ordering::SeqCst);
103//! let sub_id = SubscriptionId::Number(id as u64);
104//! let sink = subscriber.assign_id(sub_id.clone()).unwrap();
105//! self.active.write().unwrap().insert(sub_id, sink);
106//! }
107//!
108//! fn unsubscribe(&self, _meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool> {
109//! let removed = self.active.write().unwrap().remove(&id);
110//! if removed.is_some() {
111//! Ok(true)
112//! } else {
113//! Err(Error {
114//! code: ErrorCode::InvalidParams,
115//! message: "Invalid subscription.".into(),
116//! data: None,
117//! })
118//! }
119//! }
120//! }
121//!
122//! fn main() {
123//! let mut io = jsonrpc_core::MetaIoHandler::default();
124//! io.extend_with(RpcImpl::default().to_delegate());
125//!
126//! let server_builder = jsonrpc_tcp_server::ServerBuilder::with_meta_extractor(
127//! io,
128//! |request: &jsonrpc_tcp_server::RequestContext| Arc::new(Session::new(request.sender.clone()))
129//! );
130//! let server = server_builder
131//! .start(&"127.0.0.1:3030".parse().unwrap())
132//! .expect("Unable to start TCP server");
133//!
134//! // The server spawns a separate thread. Dropping the `server` handle causes it to close.
135//! // Uncomment the line below to keep the server running in your example.
136//! // server.wait();
137//! }
138//! ```
139//!
140//! Client Example
141//!
142//! ```
143//! use jsonrpc_core_client::transports::local;
144//! use jsonrpc_core::futures::{self, future};
145//! use jsonrpc_core::{IoHandler, Result, BoxFuture};
146//! use jsonrpc_derive::rpc;
147//!
148//! /// Rpc trait
149//! #[rpc]
150//! pub trait Rpc {
151//! /// Returns a protocol version
152//! #[rpc(name = "protocolVersion")]
153//! fn protocol_version(&self) -> Result<String>;
154//!
155//! /// Adds two numbers and returns a result
156//! #[rpc(name = "add", alias("callAsyncMetaAlias"))]
157//! fn add(&self, a: u64, b: u64) -> Result<u64>;
158//!
159//! /// Performs asynchronous operation
160//! #[rpc(name = "callAsync")]
161//! fn call(&self, a: u64) -> BoxFuture<Result<String>>;
162//! }
163//!
164//! struct RpcImpl;
165//!
166//! impl Rpc for RpcImpl {
167//! fn protocol_version(&self) -> Result<String> {
168//! Ok("version1".into())
169//! }
170//!
171//! fn add(&self, a: u64, b: u64) -> Result<u64> {
172//! Ok(a + b)
173//! }
174//!
175//! fn call(&self, _: u64) -> BoxFuture<Result<String>> {
176//! Box::pin(future::ready(Ok("OK".to_owned())))
177//! }
178//! }
179//!
180//! fn main() {
181//! let exec = futures::executor::ThreadPool::new().unwrap();
182//! exec.spawn_ok(run())
183//! }
184//! async fn run() {
185//! let mut io = IoHandler::new();
186//! io.extend_with(RpcImpl.to_delegate());
187//!
188//! let (client, server) = local::connect::<RpcClient, _, _>(io);
189//! let res = client.add(5, 6).await.unwrap();
190//! println!("5 + 6 = {}", res);
191//! server.await.unwrap()
192//! }
193//!
194//! ```
195
196#![recursion_limit = "256"]
197#![warn(missing_docs)]
198
199extern crate proc_macro;
200
201use proc_macro::TokenStream;
202use syn::parse_macro_input;
203
204mod options;
205mod params_style;
206mod rpc_attr;
207mod rpc_trait;
208mod to_client;
209mod to_delegate;
210
211/// Apply `#[rpc]` to a trait, and a `to_delegate` method is generated which
212/// wires up methods decorated with `#[rpc]` or `#[pubsub]` attributes.
213/// Attach the delegate to an `IoHandler` and the methods are now callable
214/// via JSON-RPC.
215#[proc_macro_attribute]
216pub fn rpc(args: TokenStream, input: TokenStream) -> TokenStream {
217 let input_toks = parse_macro_input!(input as syn::Item);
218 let args = syn::parse_macro_input!(args as syn::AttributeArgs);
219
220 let options = match options::DeriveOptions::try_from(args) {
221 Ok(options) => options,
222 Err(error) => return error.to_compile_error().into(),
223 };
224
225 match rpc_trait::rpc_impl(input_toks, &options) {
226 Ok(output) => output.into(),
227 Err(err) => err.to_compile_error().into(),
228 }
229}