Update README.md
This commit is contained in:
		
							parent
							
								
									bf183a0598
								
							
						
					
					
						commit
						bfd4c1346f
					
				
							
								
								
									
										80
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								README.md
									
									
									
									
									
								
							@ -1,58 +1,90 @@
 | 
				
			|||||||
# Eagle
 | 
					# Eagle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Disclaimer
 | 
					## Stability
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Eagle is still in development and not currently usable. The current state is barely a proof of concept.
 | 
					Eagle is still in early development. Performance is not ideal and the interface is likely to change over time. However,
 | 
				
			||||||
 | 
					it is in a usable state currently.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## What is Eagle?
 | 
					## What is Eagle?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Eagle is a library designed to make "full-stack" applications with Rust. It allows you to define a communication protocol
 | 
					Eagle is a library which allows you to easily build an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) protocol.
 | 
				
			||||||
based on simple "questions" and "answers" which can be implemented as simple functions. From the perspective of the client
 | 
					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` and uses `serde` for formatting data.
 | 
				
			||||||
(which sends "questions") the protocol is simply a set of async functions on a struct. From the perspective of the server
 | 
					 | 
				
			||||||
(which sends "answers") the protocol is a trait which it implements on any struct of its choice.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Using Eagle
 | 
					## 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. Inside this crate, you can
 | 
					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.
 | 
				
			||||||
define your protocol as an enum:
 | 
					
 | 
				
			||||||
 | 
					Inside this crate, you can define your protocol as an enum:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```rs
 | 
					```rs
 | 
				
			||||||
use eagle::Protocol;
 | 
					use eagle::Protocol;
 | 
				
			||||||
 | 
					use serde::{Serialize, Deserliaze};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					pub struct ExampleStruct {
 | 
				
			||||||
 | 
					    a: i32,
 | 
				
			||||||
 | 
					    b: i32
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Protocol)]
 | 
					#[derive(Protocol)]
 | 
				
			||||||
pub enum TestProtocol {
 | 
					pub enum Example {
 | 
				
			||||||
    Addition((i32, i32), i32),
 | 
					    Addition((i32, i32), i32),
 | 
				
			||||||
    SomeKindOfQuestion(String, i32)
 | 
					    StructuredDataAlsoWorks(ExampleStruct, ()),
 | 
				
			||||||
 | 
					    SetState(i32, i32),
 | 
				
			||||||
 | 
					    GetState((), i32)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In your server, you will be able to implement this protocol for any struct (and in the future register it for communication):
 | 
					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`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```rs
 | 
					```rs
 | 
				
			||||||
use shared::TestProtocolServer;
 | 
					use shared::ExampleServerHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Server;
 | 
					struct ExampleHandler {
 | 
				
			||||||
impl TestProtocolServer for Server {
 | 
					    state: i32
 | 
				
			||||||
    fn addition(&mut self, a: i32, b: i32) -> i32 {
 | 
					}
 | 
				
			||||||
 | 
					impl ExampleServerHandler for ExampleHandler {
 | 
				
			||||||
 | 
					    async fn addition(&mut self, a: i32, b: i32) -> i32 {
 | 
				
			||||||
        a + b
 | 
					        a + b
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn some_kind_of_question(&mut self, question: String) -> i32 {
 | 
					    async fn get_state(&mut self) -> i32 {
 | 
				
			||||||
        42
 | 
					        self.state
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn set_state(&mut self, state: i32) -> i32 {
 | 
				
			||||||
 | 
					        self.state = state;
 | 
				
			||||||
 | 
					        self.state
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* ... */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In your client, you can use an instance of the client struct to query the server:
 | 
					Your handler can now be used by the server. You can easily bind your server to a socket with:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```rs
 | 
					```rs
 | 
				
			||||||
use shared::TestProtocolClient;
 | 
					use shared::ExampleServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					let handler = ExampleHandler { state: 0 };
 | 
				
			||||||
async fn main() {
 | 
					let server_task = tokio::spawn(ExampleServer::bind(handler, "127.0.0.1:1234"));
 | 
				
			||||||
    let client = TestProtocolClient::new();
 | 
					// Or, if you're using the 'unix' feature...
 | 
				
			||||||
    assert_eq!(client.addition(2, 2).await, 4);
 | 
					let server_task = tokio::spawn(ExampleServer::bind(handler, "/tmp/sock"));
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```rs
 | 
				
			||||||
 | 
					use shared::ExampleClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let client = ExampleClient::connect("127.0.0.1:1234").await.unwrap();
 | 
				
			||||||
 | 
					assert_eq!(client.addition(5, 2), 7);
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License
 | 
					## License
 | 
				
			||||||
 | 
				
			|||||||
@ -77,7 +77,7 @@ fn derive_protocol(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream
 | 
				
			|||||||
    let query_enum_name = format_ident!("__{}Query", name);
 | 
					    let query_enum_name = format_ident!("__{}Query", name);
 | 
				
			||||||
    let queries_struct_name = format_ident!("__{}Queries", name);
 | 
					    let queries_struct_name = format_ident!("__{}Queries", name);
 | 
				
			||||||
    let client_connection_struct_name = format_ident!("__{}Connection", name);
 | 
					    let client_connection_struct_name = format_ident!("__{}Connection", name);
 | 
				
			||||||
    let server_trait_name = format_ident!("{}ServerTrait", name);
 | 
					    let server_trait_name = format_ident!("{}ServerHandler", name);
 | 
				
			||||||
    let server_connection_struct_name = format_ident!("{}Server", name);
 | 
					    let server_connection_struct_name = format_ident!("{}Server", name);
 | 
				
			||||||
    let client_struct_name = format_ident!("{}Client", name);
 | 
					    let client_struct_name = format_ident!("{}Client", name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,7 @@ enum TestProtocol {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Clone)]
 | 
					#[derive(Clone)]
 | 
				
			||||||
struct TrivialServer;
 | 
					struct TrivialServer;
 | 
				
			||||||
impl TestProtocolServerTrait for TrivialServer {
 | 
					impl TestProtocolServerHandler for TrivialServer {
 | 
				
			||||||
    async fn addition(&mut self, a: i32, b: i32) -> i32 {
 | 
					    async fn addition(&mut self, a: i32, b: i32) -> i32 {
 | 
				
			||||||
        a + b
 | 
					        a + b
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -74,7 +74,7 @@ async fn e2e() {
 | 
				
			|||||||
    #[cfg(feature = "tcp")]
 | 
					    #[cfg(feature = "tcp")]
 | 
				
			||||||
    let address = format!("127.0.0.1:{}", 10000 + rand::random::<u64>() % 1000);
 | 
					    let address = format!("127.0.0.1:{}", 10000 + rand::random::<u64>() % 1000);
 | 
				
			||||||
    let server_task = tokio::spawn(TestProtocolServer::bind(TrivialServer, address.clone()));
 | 
					    let server_task = tokio::spawn(TestProtocolServer::bind(TrivialServer, address.clone()));
 | 
				
			||||||
    // Wait for the server to start
 | 
					    // Wait for the server to start, the developer is responsible for this in production
 | 
				
			||||||
    tokio::time::sleep(std::time::Duration::from_millis(10)).await;
 | 
					    tokio::time::sleep(std::time::Duration::from_millis(10)).await;
 | 
				
			||||||
    let client = TestProtocolClient::connect(address).await.unwrap();
 | 
					    let client = TestProtocolClient::connect(address).await.unwrap();
 | 
				
			||||||
    assert_eq!(client.addition(2, 5).await.unwrap(), 7);
 | 
					    assert_eq!(client.addition(2, 5).await.unwrap(), 7);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user