Simplify the macro.

No longer create a submodule. It's up to the user to decide how to scope the macro invocation.
The preferred method will be to invoke within a dedicated rpc module.
This commit is contained in:
Tim Kuehn
2016-01-27 21:11:38 -08:00
parent 69f4c954fa
commit 41c036fa0f
3 changed files with 173 additions and 307 deletions

View File

@@ -9,11 +9,9 @@ tarpc is an RPC framework for rust with a focus on ease of use. Defining and imp
extern crate tarpc;
extern crate serde;
rpc! {
mod hello_service {
service {
rpc hello(name: String) -> String;
}
mod hello_service {
service! {
rpc hello(name: String) -> String;
}
}
@@ -33,11 +31,18 @@ fn main() {
}
```
The `rpc!` macro generates a module in the current module. In the above example, the module is named `hello_service`. This module will contain a `Client` type, a `Service` trait, and a `serve` function. `serve` can be used to start a server listening on a tcp port. A `Client` can connect to such a service. Any type implementing the `Service` trait can be passed to `serve`. These generated types are specific to the echo service, and make it easy and ergonomic to write servers without dealing with sockets or serialization directly. See the tarpc_examples package for more sophisticated examples.
The `service!` macro expands to a collection of items that collectively form an rpc service. In the
above example, the macro is called within the `hello_service` module. This module will contain a
`Client` type, a `Service` trait, and a `serve` function. `serve` can be used to start a server
listening on a tcp port. A `Client` can connect to such a service. Any type implementing the
`Service` trait can be passed to `serve`. These generated types are specific to the echo service,
and make it easy and ergonomic to write servers without dealing with sockets or serialization
directly. See the tarpc_examples package for more sophisticated examples.
## Additional Features
- Imports can be specified in an `item {}` block that appears above the `service {}` block.
- Attributes can be specified on rpc methods. These will be included on both the `Service` trait methods as well as on the `Client`'s stub methods.
- Attributes can be specified on rpc methods. These will be included on both the `Service` trait
methods as well as on the `Client`'s stub methods.
## Planned Improvements (actively being worked on)
- Automatically reconnect on the client side when the connection cuts out.

View File

@@ -15,12 +15,10 @@
//! # #![plugin(serde_macros)]
//! # #[macro_use] extern crate tarpc;
//! # extern crate serde;
//! rpc! {
//! mod my_server {
//! service {
//! rpc hello(name: String) -> String;
//! rpc add(x: i32, y: i32) -> i32;
//! }
//! mod my_server {
//! service! {
//! rpc hello(name: String) -> String;
//! rpc add(x: i32, y: i32) -> i32;
//! }
//! }
//!

View File

@@ -10,70 +10,6 @@
#[macro_export]
macro_rules! as_item { ($i:item) => {$i} }
// Inserts a placeholder doc comment for the module if it's missing
#[doc(hidden)]
#[macro_export]
macro_rules! add_mod_doc {
// If nothing left, return
(
@rec
{ $(#[$done:meta])* }
{ }
$i:item
) => {
$(#[$done])*
#[doc="A module containing an rpc service and client stub."]
$i
};
// If we find a doc attribute, return
(
@rec
{ $(#[$done:meta])* }
{
#[doc=$doc:expr]
$(#[$rest:meta])*
}
$i:item
) => {
$(#[$done])*
#[doc=$doc]
$(#[$rest])*
$i
};
// If we don't find a doc attribute, keep going
(
@rec
{ $(#[$($done:tt)*])* }
{
#[$($attr:tt)*]
$($rest:tt)*
}
$i:item
) => {
add_mod_doc! {
@rec
{ $(#[$($done)*])* #[$($attr)*] }
{ $($rest)* }
$i
}
};
// Entry
(
{ $(#[$($attr:tt)*])* }
$i:item
) => {
add_mod_doc! {
@rec
{}
{ $(#[$($attr)*])* }
$i
}
};
}
// Required because if-let can't be used with irrefutable patterns, so it needs
// to be special cased.
#[doc(hidden)]
@@ -176,178 +112,126 @@ macro_rules! request_variant {
// The main macro that creates RPC services.
#[macro_export]
macro_rules! rpc {
macro_rules! service {
(
$(#[$($service_attr:tt)*])*
mod $server:ident {
service {
$(
$(#[$attr:meta])*
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
)*
}
}
// List any rpc methods: rpc foo(arg1: Arg1, ..., argN: ArgN) -> Out
$(
$(#[$attr:meta])*
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
)*
) => {
rpc! {
$(#[$($service_attr)*])*
mod $server {
#[doc="The provided RPC service."]
pub trait Service: Send + Sync {
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out;
)*
}
items { }
service {
$(
$(#[$attr])*
rpc $fn_name($($arg: $in_),*) -> $out;
)*
impl<P, S> Service for P
where P: Send + Sync + ::std::ops::Deref<Target=S>,
S: Service
{
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out {
Service::$fn_name(&**self, $($arg),*)
}
)*
}
define_request!($($fn_name($($in_),*))*);
#[allow(non_camel_case_types)]
#[derive(Debug, Serialize, Deserialize)]
enum __Reply {
$(
$fn_name($out),
)*
}
/// An asynchronous RPC call
pub struct Future<T> {
future: $crate::protocol::Future<__Reply>,
mapper: fn(__Reply) -> T,
}
impl<T> Future<T> {
/// Block until the result of the RPC call is available
pub fn get(self) -> $crate::Result<T> {
self.future.get().map(self.mapper)
}
}
};
(
// Names the service
$(#[$($service_attr:tt)*])*
mod $server:ident {
#[doc="The client stub that makes RPC calls to the server."]
pub struct Client($crate::protocol::Client<__Request, __Reply>);
// Include any desired or required items. Conflicts can arise with the following names:
// 1. Service
// 2. Client
// 3. serve
// 4. __Reply
// 5. __Request
items { $($i:item)* }
impl Client {
#[doc="Create a new client that connects to the given address."]
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<Self>
where A: ::std::net::ToSocketAddrs,
{
let inner = try!($crate::protocol::Client::new(addr, timeout));
Ok(Client(inner))
}
// List any rpc methods: rpc foo(arg1: Arg1, ..., argN: ArgN) -> Out
service {
client_methods!(
$(
$(#[$attr:meta])*
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
{ $(#[$attr])* }
$fn_name($($arg: $in_),*) -> $out
)*
);
}
#[doc="The client stub that makes asynchronous RPC calls to the server."]
pub struct AsyncClient($crate::protocol::Client<__Request, __Reply>);
impl AsyncClient {
#[doc="Create a new asynchronous client that connects to the given address."]
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<Self>
where A: ::std::net::ToSocketAddrs,
{
let inner = try!($crate::protocol::Client::new(addr, timeout));
Ok(AsyncClient(inner))
}
async_client_methods!(
$(
{ $(#[$attr])* }
$fn_name($($arg: $in_),*) -> $out
)*
);
}
struct __Server<S: 'static + Service>(S);
impl<S> $crate::protocol::Serve for __Server<S>
where S: 'static + Service
{
type Request = __Request;
type Reply = __Reply;
fn serve(&self, request: __Request) -> __Reply {
match request {
$(
request_variant!($fn_name $($arg),*) =>
__Reply::$fn_name((self.0).$fn_name($($arg),*)),
)*
}
}
}
) => {
add_mod_doc! {
{ $(#[$($service_attr)*])* }
pub mod $server {
$($i)*
#[doc="The provided RPC service."]
pub trait Service: Send + Sync {
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out;
)*
}
impl<P, S> Service for P
where P: Send + Sync + ::std::ops::Deref<Target=S>,
S: Service
{
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out {
Service::$fn_name(&**self, $($arg),*)
}
)*
}
define_request!($($fn_name($($in_),*))*);
#[allow(non_camel_case_types)]
#[derive(Debug, Serialize, Deserialize)]
enum __Reply {
$(
$fn_name($out),
)*
}
/// An asynchronous RPC call
pub struct Future<T> {
future: $crate::protocol::Future<__Reply>,
mapper: fn(__Reply) -> T,
}
impl<T> Future<T> {
/// Block until the result of the RPC call is available
pub fn get(self) -> $crate::Result<T> {
self.future.get().map(self.mapper)
}
}
#[doc="The client stub that makes RPC calls to the server."]
pub struct Client($crate::protocol::Client<__Request, __Reply>);
impl Client {
#[doc="Create a new client that connects to the given address."]
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<Self>
where A: ::std::net::ToSocketAddrs,
{
let inner = try!($crate::protocol::Client::new(addr, timeout));
Ok(Client(inner))
}
client_methods!(
$(
{ $(#[$attr])* }
$fn_name($($arg: $in_),*) -> $out
)*
);
}
#[doc="The client stub that makes asynchronous RPC calls to the server."]
pub struct AsyncClient($crate::protocol::Client<__Request, __Reply>);
impl AsyncClient {
#[doc="Create a new asynchronous client that connects to the given address."]
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<Self>
where A: ::std::net::ToSocketAddrs,
{
let inner = try!($crate::protocol::Client::new(addr, timeout));
Ok(AsyncClient(inner))
}
async_client_methods!(
$(
{ $(#[$attr])* }
$fn_name($($arg: $in_),*) -> $out
)*
);
}
struct __Server<S: 'static + Service>(S);
impl<S> $crate::protocol::Serve for __Server<S>
where S: 'static + Service
{
type Request = __Request;
type Reply = __Reply;
fn serve(&self, request: __Request) -> __Reply {
match request {
$(
request_variant!($fn_name $($arg),*) =>
__Reply::$fn_name((self.0).$fn_name($($arg),*)),
)*
}
}
}
#[doc="Start a running service."]
pub fn serve<A, S>(addr: A,
service: S,
read_timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<$crate::protocol::ServeHandle>
where A: ::std::net::ToSocketAddrs,
S: 'static + Service
{
let server = ::std::sync::Arc::new(__Server(service));
Ok(try!($crate::protocol::serve_async(addr, server, read_timeout)))
}
}
#[doc="Start a running service."]
pub fn serve<A, S>(addr: A,
service: S,
read_timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<$crate::protocol::ServeHandle>
where A: ::std::net::ToSocketAddrs,
S: 'static + Service
{
let server = ::std::sync::Arc::new(__Server(service));
Ok(try!($crate::protocol::serve_async(addr, server, read_timeout)))
}
}
}
@@ -363,28 +247,23 @@ mod test {
Some(Duration::from_secs(5))
}
rpc! {
#[deny(missing_docs)]
#[doc="Hello"]
mod my_server {
items {
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Foo {
pub message: String
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Foo {
pub message: String
}
service {
rpc hello(foo: Foo) -> Foo;
rpc add(x: i32, y: i32) -> i32;
}
mod my_server {
use super::Foo;
service! {
rpc hello(foo: Foo) -> Foo;
rpc add(x: i32, y: i32) -> i32;
}
}
use self::my_server::*;
struct Server;
impl Service for Server {
impl my_server::Service for Server {
fn hello(&self, s: Foo) -> Foo {
Foo { message: format!("Hello, {}", &s.message) }
}
@@ -396,7 +275,7 @@ mod test {
#[test]
fn serve_arc_server() {
serve("localhost:0", ::std::sync::Arc::new(Server), None)
my_server::serve("localhost:0", ::std::sync::Arc::new(Server), None)
.unwrap()
.shutdown();
}
@@ -404,7 +283,7 @@ mod test {
#[test]
fn simple() {
let handle = my_server::serve( "localhost:0", Server, test_timeout()).unwrap();
let client = Client::new(handle.local_addr(), None).unwrap();
let client = my_server::Client::new(handle.local_addr(), None).unwrap();
assert_eq!(3, client.add(1, 2).unwrap());
let foo = Foo { message: "Adam".into() };
let want = Foo { message: format!("Hello, {}", &foo.message) };
@@ -416,7 +295,7 @@ mod test {
#[test]
fn simple_async() {
let handle = my_server::serve("localhost:0", Server, test_timeout()).unwrap();
let client = AsyncClient::new(handle.local_addr(), None).unwrap();
let client = my_server::AsyncClient::new(handle.local_addr(), None).unwrap();
assert_eq!(3, client.add(1, 2).get().unwrap());
let foo = Foo { message: "Adam".into() };
let want = Foo { message: format!("Hello, {}", &foo.message) };
@@ -425,63 +304,48 @@ mod test {
handle.shutdown();
}
// Tests a service definition with a fn that takes no args
rpc! {
mod qux {
service {
rpc hello() -> String;
}
/// Tests a service definition with a fn that takes no args
mod qux {
service! {
rpc hello() -> String;
}
}
// Tests a service definition with an import
rpc! {
mod foo {
items {
use std::collections::HashMap;
}
/// Tests a service definition with an import
mod foo {
use std::collections::HashMap;
service {
#[doc="Hello bob"]
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
service! {
#[doc="Hello bob"]
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
}
// Tests a service definition with an attribute but no doc comment
rpc! {
#[deny(missing_docs)]
mod bar {
items {
use std::collections::HashMap;
}
/// Tests a service definition with an attribute but no doc comment
#[deny(missing_docs)]
mod bar {
use std::collections::HashMap;
service {
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
service! {
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
}
// Tests a service definition with an attribute and a doc comment
rpc! {
#[deny(missing_docs)]
#[doc="Hello bob"]
#[allow(unused)]
mod baz {
items {
use std::collections::HashMap;
/// Tests a service definition with an attribute and a doc comment
#[deny(missing_docs)]
#[allow(unused)]
mod baz {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Debuggable;
}
#[derive(Debug)]
pub struct Debuggable;
service {
#[doc="Hello bob"]
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
service! {
#[doc="Hello bob"]
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
}
@@ -490,16 +354,15 @@ mod test {
println!("{:?}", baz::Debuggable);
}
rpc! {
mod hello {
service {
rpc hello(s: String) -> String;
}
mod hi {
service! {
rpc hello(s: String) -> String;
}
}
struct HelloServer;
impl hello::Service for HelloServer {
impl hi::Service for HelloServer {
fn hello(&self, s: String) -> String {
format!("Hello, {}!", s)
}
@@ -508,8 +371,8 @@ mod test {
#[bench]
fn hello(bencher: &mut Bencher) {
let _ = env_logger::init();
let handle = hello::serve("localhost:0", HelloServer, None).unwrap();
let client = hello::AsyncClient::new(handle.local_addr(), None).unwrap();
let handle = hi::serve("localhost:0", HelloServer, None).unwrap();
let client = hi::AsyncClient::new(handle.local_addr(), None).unwrap();
let concurrency = 100;
let mut rpcs = Vec::with_capacity(concurrency);
bencher.iter(|| {