diff --git a/tarpc/README.md b/tarpc/README.md new file mode 100644 index 0000000..35ea58b --- /dev/null +++ b/tarpc/README.md @@ -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 diff --git a/tarpc/src/lib.rs b/tarpc/src/lib.rs index 64789aa..dd6109c 100644 --- a/tarpc/src/lib.rs +++ b/tarpc/src/lib.rs @@ -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::*; //! diff --git a/tarpc/src/macros.rs b/tarpc/src/macros.rs index b12433d..8a4f352 100644 --- a/tarpc/src/macros.rs +++ b/tarpc/src/macros.rs @@ -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; + } + } } }