mirror of
https://github.com/OMGeeky/tarpc.git
synced 2025-12-28 07:12:05 +01:00
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:
19
README.md
19
README.md
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
|
||||
@@ -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(|| {
|
||||
|
||||
Reference in New Issue
Block a user