From 812861640de2a81f8475e52b445f239cc077110e Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Mon, 24 Jun 2024 18:26:19 +0200 Subject: [PATCH] Significantly update the documentation --- .gitea/workflows/build.yaml | 14 +++- Cargo.lock | 53 ++++++++++++ Cargo.toml | 1 + README.md | 25 +++--- flake.nix | 6 ++ src/lib.rs | 162 +++++++++++++++++++++++++++++++++++- tests/client.rs | 2 +- tests/full.rs | 2 +- 8 files changed, 247 insertions(+), 18 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index b69795d..5dd20e0 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -15,4 +15,16 @@ jobs: - name: Run clippy run: nix build .#clippy_${{ matrix.feature }} - name: Build & test - run: nix build .#${{ matrix.feature }} \ No newline at end of file + run: nix build .#${{ matrix.feature }} + docs: + runs-on: nix + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build docs + run: nix build .#doc.doc + - name: (Temporary) Upload docs + uses: actions/upload-artifact@v2 + with: + name: docs + path: result-doc/* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 94ebbd6..8352a48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -142,6 +164,7 @@ dependencies = [ "serde", "syn", "tokio", + "tokio-test", ] [[package]] @@ -167,6 +190,12 @@ dependencies = [ "log", ] +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + [[package]] name = "getrandom" version = "0.2.15" @@ -435,6 +464,30 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index afee0cd..5c8ec4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ log = { version = "0.4.21", optional = true } tokio = { version = "1.38.0", features = ["sync", "rt-multi-thread", "macros", "time", "io-util", "net"] } env_logger = "0.11.3" log = "0.4.21" +tokio-test = "0.4.4" [lib] proc-macro = true diff --git a/README.md b/README.md index f81d261..0ca5b6d 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,12 @@ ## Stability -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. +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` and uses `serde` for formatting data. +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. ## Using Eagle @@ -16,9 +15,9 @@ The way that `eagle` is designed to be used is inside a shared dependency betwee Inside this crate, you can define your protocol as an enum: -```rs +```rust use eagle::Protocol; -use serde::{Serialize, Deserliaze}; +use serde::{Serialize, Deserialize}; #[derive(Clone, Serialize, Deserialize)] pub struct ExampleStruct { @@ -35,14 +34,14 @@ pub enum Example { } ``` -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`. +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: - -```rs -use shared::ExampleServerHandler; +protocol. A handler must implement [`Clone`] as well as the `ServerHandler` trait for your protocol. For the above example: +```rust struct ExampleHandler { state: i32 } @@ -59,14 +58,12 @@ impl ExampleServerHandler for ExampleHandler { self.state = state; self.state } - - /* ... */ } ``` Your handler can now be used by the server. You can easily bind your server to a socket with: -```rs +```rust use shared::ExampleServer; let handler = ExampleHandler { state: 0 }; @@ -80,7 +77,7 @@ Note that bind is an asynchronous function which should never return, you must p On the client, all you need to do is to use your protocol's `Client` to connect and you can start making requests. -```rs +```rust use shared::ExampleClient; let client = ExampleClient::connect("127.0.0.1:1234").await.unwrap(); diff --git a/flake.nix b/flake.nix index 274dad6..ff60bf5 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,12 @@ doCheck = true; mode = "test"; }; + doc = naersk-lib.buildPackage { + src = ./.; + doDoc = true; + mode = "test"; + cargoDocOptions = x: x ++ ["--no-deps"]; + }; unix = naersk-lib.buildPackage { src = ./.; doCheck = true; diff --git a/src/lib.rs b/src/lib.rs index f0a8fb8..858f204 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ /* -Eagle - A library for easy communication in full-stack Rust applications +Eagle - A simple library for RPC in Rust Copyright (c) 2024 KodiCraft This program is free software: you can redistribute it and/or modify @@ -15,6 +15,139 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ + +//! # Eagle - A simple library for RPC in Rust +//! +//!
Eagle is still in early development. This documentation is subject to change and may not be entirely accurate.
+//! +//! Eagle is a library for building RPC protocols in Rust. It uses a macro +//! to transform your protocol definition into the necessary code to allow +//! communication between a server and a client. +//! +//! Eagle uses [`tokio`](https://tokio.rs) as its async runtime and +//! [`ron`](https://crates.io/crates/ron) for serialization. +//! +//! ## Usage +//! `eagle` is designed to be used to create your own protocol crate. We +//! recommend creating a new cargo workspace for your project with a shared +//! crate which will contain your protocol definition and individual crates +//! for the server and client. +//! +//! In your shared crate, you can define your protocol using the [`Protocol`] +//! derive macro. This will generate the necessary code for the server and +//! client to communicate. +//! +//! ```rust +//! use eagle::Protocol; +//! +//! #[derive(Protocol)] +//! pub enum Example { +//! Add((i32, i32), i32), +//! Length(String, usize), +//! /* ... */ +//! } +//! ``` +//! +//! The [`Protocol`] derive macro will generate all the necessary code, including +//! your handler trait, your server struct, and your client struct. +//! +//! On your server, you will need to implement the handler trait. This trait +//! describes the functions that the client can request from the server. +//! +//! ```rust +//! # use eagle::Protocol; +//! # #[derive(Protocol)] +//! # pub enum Example { +//! # Add((i32, i32), i32), +//! # Length(String, usize), +//! # /* ... */ +//! # } +//! # +//! #[derive(Clone)] +//! pub struct Handler; +//! impl ExampleServerHandler for Handler { +//! async fn add(&mut self, a: i32, b: i32) -> i32 { +//! a + b +//! } +//! async fn length(&mut self, s: String) -> usize { +//! s.len() +//! } +//! /* ... */ +//! } +//! ``` +//! +//! To start the server, you simply need to use the generated server struct and +//! pass it your handler. +//! +//! ```no_run +//! # use eagle::Protocol; +//! # #[derive(Protocol)] +//! # pub enum Example { +//! # Add((i32, i32), i32), +//! # Length(String, usize), +//! # /* ... */ +//! # } +//! # +//! # #[derive(Clone)] +//! # pub struct Handler; +//! # impl ExampleServerHandler for Handler { +//! # async fn add(&mut self, a: i32, b: i32) -> i32 { +//! # a + b +//! # } +//! # async fn length(&mut self, s: String) -> usize { +//! # s.len() +//! # } +//! # } +//! # +//! # tokio_test::block_on(async { +//! let handler = Handler; +//! let address = "127.0.0.1:12345"; // Or, if using the 'unix' feature, "/tmp/eagle.sock" +//! let server_task = tokio::spawn(ExampleServer::bind(handler, address)); +//! # }); +//! ``` +//! +//! Please note the usage of `tokio::spawn`. This is because the `bind` function +//! will not return until the server is closed. You can use the `abort` method +//! on the task to close the server. +//! +//! On the client side, you can simply use the generated client struct to connect +//! to the server and begin sending queries. +//! +//! ```no_run +//! # use eagle::Protocol; +//! # #[derive(Protocol)] +//! # pub enum Example { +//! # Add((i32, i32), i32), +//! # Length(String, usize), +//! # /* ... */ +//! # } +//! # +//! # #[derive(Clone)] +//! # pub struct Handler; +//! # impl ExampleServerHandler for Handler { +//! # async fn add(&mut self, a: i32, b: i32) -> i32 { +//! # a + b +//! # } +//! # async fn length(&mut self, s: String) -> usize { +//! # s.len() +//! # } +//! # } +//! # +//! # tokio_test::block_on(async { +//! # let handler = Handler; +//! let address = "127.0.0.1:12345"; // Or, if using the 'unix' feature, "/tmp/eagle.sock" +//! # let server_task = tokio::spawn(ExampleServer::bind(handler, address)); +//! let client = ExampleClient::connect(address).await.unwrap(); +//! # // Wait for the server to start, the developer is responsible for this in production +//! # tokio::time::sleep(std::time::Duration::from_millis(10)).await; +//! assert_eq!(client.add(2, 5).await.unwrap(), 7); +//! # }); +//! ``` +//! +//! The client can be closed by calling the `close` method on the client struct. +//! This will abort the connection. + +#![warn(missing_docs)] use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{parse2, spanned::Spanned, DeriveInput, Field, Ident}; @@ -26,6 +159,33 @@ compile_error!("You must enable either the 'tcp' or 'unix' feature"); #[cfg(all(feature = "unix", not(unix)))] compile_error!("The 'unix' feature requires compiling for a unix target"); +/// Generate all the necessary RPC code for a protocol from an enum describing it. +/// +/// This macro will generate various enums and structs to enable communication +/// between a server and a client. The following items will be generated, where {} +/// is the name of the protocol enum: +/// - `{}ServerHandler` - A trait that the server must implement to handle queries +/// - `{}Server` - A struct that the server uses to communicate with clients +/// - `{}Client` - A struct that the client uses to communicate with a server +/// +/// Each variant of the passed enum represents a query that the client can send to the +/// server. The first field of each variant is the question (serverbound), the second field +/// is the answer (clientbound). You may use tuples to represent sending multiple arguments and +/// you may use the unit type `()` to represent no arguments. Only data types which implement +/// [`Clone`], [`serde::Serialize`], and [`serde::Deserialize`] can be used. +/// +/// For more information on how to use the generated code, see the [crate-level documentation](index.html). +/// +/// # Example +/// ```rust +/// use eagle::Protocol; +/// +/// #[derive(Protocol)] +/// pub enum Example { +/// Add((i32, i32), i32), +/// Length(String, usize), +/// } +/// ``` #[proc_macro_derive(Protocol)] pub fn derive_protocol_derive(input: TokenStream) -> TokenStream { let expanded = derive_protocol(input.into()); diff --git a/tests/client.rs b/tests/client.rs index 11aabf4..7181f70 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,5 +1,5 @@ /* -Eagle - A library for easy communication in full-stack Rust applications +Eagle - A simple library for RPC in Rust Copyright (c) 2024 KodiCraft This program is free software: you can redistribute it and/or modify diff --git a/tests/full.rs b/tests/full.rs index c5624ce..cbb68f8 100644 --- a/tests/full.rs +++ b/tests/full.rs @@ -1,5 +1,5 @@ /* -Eagle - A library for easy communication in full-stack Rust applications +Eagle - A simple library for RPC in Rust Copyright (c) 2024 KodiCraft This program is free software: you can redistribute it and/or modify