# Eagle ## Stability Eagle is still in early development. Performance is not ideal, the interface is likely to change and the documentation is not final. Basic functionality is fully implemented and works as expected. ## What is Eagle? Eagle is a library which allows you to easily build an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) protocol. It uses a macro to generate the required communication code and makes adding new functions easy and quick. Eagle is designed to work specifically with [`tokio`](https://crates.io/crates/tokio) and uses [`serde`](https://crates.io/crates/serde) for formatting data. Please note that since `eagle` is a pure proc-macro library, you must manually add compatible versions of `tokio`, `serde`, `ron` and optionally `log` to your dependencies. ## Using Eagle The way that `eagle` is designed to be used is inside a shared dependency between your "server" and your "client". Both of these should be in a workspace. Create a `shared` crate which both components should depend on, this crate should have `eagle` as a dependency. By default `eagle` uses TCP for communication, but you may disable default features and enable the `unix` feature on `eagle` to use unix sockets instead. Inside this crate, you can define your protocol as an enum: ```rust use eagle::Protocol; use serde::{Serialize, Deserialize}; #[derive(Clone, Serialize, Deserialize)] pub struct ExampleStruct { a: i32, b: i32 } #[derive(Protocol)] pub enum Example { Addition((i32, i32), i32), StructuredDataAlsoWorks(ExampleStruct, ()), SetState(i32, i32), GetState((), i32) } ``` Each variant describes one of the functions that the client can call, the first field on a variant represents the arguments that the client can send and the second field represents the return value. In the example above, the `addition` function would take in two [`i32`]s and return another [`i32`]. Any data passed this way must implement [`Clone`] as well as [`serde::Serialize`] and [`serde::Deserialize`]. The [`Protocol`] macro will create a number of exports in your shared crate. You will be able to import them by name in your client and server. Once your protocol is defined, you can implement it on your server. To do so, you must first implement a handler for your protocol. A handler must implement [`Clone`] as well as the `ServerHandler` trait for your protocol. For the above example: ```rust struct ExampleHandler { state: i32 } impl ExampleServerHandler for ExampleHandler { async fn addition(&mut self, a: i32, b: i32) -> i32 { a + b } async fn get_state(&mut self) -> i32 { self.state } async fn set_state(&mut self, state: i32) -> i32 { self.state = state; self.state } } ``` Your handler can now be used by the server. You can easily bind your server to a socket with: ```rust use shared::ExampleServer; let handler = ExampleHandler { state: 0 }; let server_task = ExampleServer::bind(handler, "127.0.0.1:1234").await; // Or, if you're using the 'unix' feature... let server_task = ExampleServer::bind(handler, "/tmp/sock").await; ``` Note that bind is an asynchronous function which should never return, you must put it in a separate task. Once bound, the server will await for connections and start responding to queries. On the client, all you need to do is to use your protocol's `Client` to connect and you can start making requests. ```rust use shared::ExampleClient; let client = ExampleClient::connect("127.0.0.1:1234").await.unwrap(); assert_eq!(client.addition(5, 2), 7); ``` ## License Eagle is licensed under the AGPL (GNU Affero General Public License). To learn more, read [LICENSE.md](LICENSE.md)