Clean up the syntax; add the README.md (I forgot to git add it last time)

This commit is contained in:
Tim Kuehn
2016-01-12 21:54:01 -08:00
parent adb77924e2
commit 1f4a72b0f9
3 changed files with 129 additions and 23 deletions

44
tarpc/README.md Normal file
View File

@@ -0,0 +1,44 @@
## tarpc
tarpc is an RPC framework for rust with a focus on ease of use. Defining and implementing an echo-like server can be done in just a few lines of code:
```rust
#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]
#[macro_use]
extern crate tarpc;
extern crate serde;
rpc! {
mod hello_service {
service {
hello(name: String) -> String;
}
}
}
impl hello_service::Service for () {
fn hello(&self, name: String) -> String {
format!("Hello, {}!", s)
}
}
fn main() {
let server_handle = hello_service::serve("0.0.0.0:0", ()).unwrap();
let client = hello_service::Client::new(server_handle.local_addr()).unwrap();
assert_eq!("Hello, Mom!".into(), client.hello("Mom".into()).unwrap());
drop(client);
server_handle.shutdown();
}
```
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.
## Planned Improvements (actively being worked on)
- Automatically reconnect on the client side when the connection cuts out.
- Allow omitting the return type in rpc definitions when the type is `()`.
- Allow users to specify imports inside the `rpc!` macro
- Support arbitrary serialization. (currently `serde_json` is used for all serialization)
- Support asynchronous server implementations (currently thread per connection).
- Support doc comments on rpc method definitions

View File

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

View File

@@ -1,7 +1,8 @@
#[macro_export]
macro_rules! as_item { ($i:item) => {$i} }
// Required because if-let can't be used with irrefutable patterns, so it needs to be special
// Required because if-let can't be used with irrefutable patterns, so it needs
// to be special
// cased.
#[macro_export]
macro_rules! request_fns {
@@ -48,11 +49,46 @@ macro_rules! request_variant {
// The main macro that creates RPC services.
#[macro_export]
macro_rules! rpc_service { ($server:ident:
$( rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;)*) => {
macro_rules! rpc {
(
mod $server:ident {
service {
$( $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;)*
}
}
) => {
rpc! {
mod $server {
items { }
service { $( $fn_name($($arg: $in_),*) -> $out;)* }
}
}
};
(
// Names the service
mod $server:ident {
// 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)* }
// List any rpc methods: rpc foo(arg1: Arg1, ..., argN: ArgN) -> Out
service { $( $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;)* }
}
) => {
#[doc="A module containing an rpc service and client stub."]
pub mod $server {
$($i)*
#[doc="The provided RPC service."]
pub trait Service: Send + Sync {
$(
@@ -115,22 +151,27 @@ macro_rules! rpc_service { ($server:ident:
#[cfg(test)]
#[allow(dead_code)]
mod test {
rpc_service!(my_server:
rpc hello(foo: super::Foo) -> super::Foo;
rpc! {
mod my_server {
items {
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Foo {
pub message: String
}
}
rpc add(x: i32, y: i32) -> i32;
);
service {
hello(foo: Foo) -> Foo;
add(x: i32, y: i32) -> i32;
}
}
}
use self::my_server::*;
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Foo {
message: String
}
impl Service for () {
fn hello(&self, s: Foo) -> Foo {
Foo{message: format!("Hello, {}", &s.message)}
Foo { message: format!("Hello, {}", &s.message) }
}
fn add(&self, x: i32, y: i32) -> i32 {
@@ -145,15 +186,32 @@ mod test {
let shutdown = my_server::serve(addr, ()).unwrap();
let client = Client::new(addr).unwrap();
assert_eq!(3, client.add(1, 2).unwrap());
let foo = Foo{message: "Adam".into()};
let want = Foo{message: format!("Hello, {}", &foo.message)};
assert_eq!(want, client.hello(Foo{message: "Adam".into()}).unwrap());
let foo = Foo { message: "Adam".into() };
let want = Foo { message: format!("Hello, {}", &foo.message) };
assert_eq!(want, client.hello(Foo { message: "Adam".into() }).unwrap());
drop(client);
shutdown.shutdown();
}
// This is a test of a service with a fn that takes no args
rpc_service! {foo:
rpc hello() -> String;
// Tests a service definition with a fn that takes no args
rpc! {
mod foo {
service {
hello() -> String;
}
}
}
// Tests a service definition with an import
rpc! {
mod bar {
items {
use std::collections::HashMap;
}
service {
baz(s: String) -> HashMap<String, String>;
}
}
}
}