Skip to main content

injoint/
lib.rs

1//! Injoint is a library for creating and managing WebSocket connections in declarative, inspired by state reducer approach.
2//!
3//! # About Injoint
4//! Core concept of injoint is `Joint` structure - a wrapper for all multithreaded asynchronous functionality
5//! that is necessary to run a WebSocket server. It is responsible for managing the state of the application,
6//! handling incoming messages, and dispatching them to the appropriate reducers for each room individually.
7//!
8//! # Joint implementations
9//! `Joint` structure is heuristically abstract and need to be implemented around some real-life conception,
10//! for example, websockets or mpsc. `injoint` library currently provides these implementations for `AbstractJoint`:
11//! - [`WebsocketJoint`](joint::ws::WebsocketJoint) - common implementation around asynchronous websocket connection
12//! using `tokio` and `tungstenite` libraries.
13//! - [`AxumWSJoint`](joint::axum::AxumWSJoint) - another implementation around websocket that can be integrated
14//! into `axum` router.
15//! - [`MPSCJoint`](joint::mpsc::MPSCJoint) - implementation around `tokio::sync::mpsc` channels.
16//!
17//! # Usage
18//! Example of minimalistic websocket chat server taken from [GitHub repository](https://github.com/PixelQuasar/injoint):
19//!
20//! ```rust
21//! use injoint::codegen::{reducer_actions, Broadcastable};
22//! use injoint::joint::ws::WebsocketJoint;
23//! use serde::Serialize;
24//! use std::collections::HashMap;
25//!
26//! // message struct, used in State
27//! #[derive(Serialize, Debug, Clone, Broadcastable)]
28//! struct Message {
29//!     pub author: u64,
30//!     pub content: String,
31//! }
32//!
33//! // chat state struct
34//! #[derive(Serialize, Debug, Default, Clone, Broadcastable)]
35//! struct State {
36//!     users: HashMap<u64, String>,
37//!     messages: Vec<Message>,
38//! }
39//!
40//! // state reducer, statically injected to `WebsocketJoint`
41//! #[derive(Default, Serialize, Clone, Broadcastable)]
42//! struct Reducer {
43//!     state: State,
44//! }
45//!
46//! impl Reducer {
47//!     pub fn new() -> Self {
48//!         Reducer {
49//!             state: State {
50//!                 users: HashMap::new(),
51//!                 messages: Vec::new(),
52//!             },
53//!         }
54//!     }
55//! }
56//!
57//! // using `reducer_actions` macro to generate boilerplate
58//! // code implementing actions and their dispatching
59//! #[reducer_actions(State)]
60//! impl Reducer {
61//!     async fn identify_user(&mut self, client_id: u64, name: String) -> Result<String, String> {
62//!         if self.state.users.contains_key(&client_id) {
63//!             return Err("User already identified".to_string());
64//!         }
65//!         self.state.users.insert(client_id, name.clone());
66//!         Ok(name)
67//!     }
68//!
69//!     async fn send_message(&mut self, client_id: u64, text: String) -> Result<String, String> {
70//!         if !self.state.users.contains_key(&client_id) {
71//!             return Err("User not identified".to_string());
72//!         }
73//!         self.state.messages.push(Message {
74//!             author: client_id,
75//!             content: text.clone(),
76//!         });
77//!         Ok(text)
78//!     }
79//! }
80//!
81//! #[tokio::main]
82//! async fn main() {
83//!     // initialize default reducer state
84//!     let reducer = Reducer::new();
85//!
86//!     // create root websocket joint instance
87//!     let mut joint = WebsocketJoint::<Reducer>::new(reducer);
88//!
89//!     // bind address to listen on
90//!     joint.bind_addr("127.0.0.1:3000").await.unwrap();
91//!
92//!    // start listening loop handling incoming connections
93//!     joint.listen().await;
94//! }
95//! ```
96//!
97//! #### And then just build and run it with
98//! ```bash
99//! cargo run
100//! ```
101//!
102//! #### As websocket client, you may send one of four types of methods:
103//! - `Create` - create a new room
104//! example:
105//! ```json
106//! {
107//! "message": {
108//!     "type": "Create",
109//! },
110//! "client_token": "" // doesn't affect builtin logic, you can use this token to identify your client
111//! }
112//! ```
113//! - `Join` - join an existing room by id
114//! example:
115//! ```json
116//! {
117//! "message": {
118//!     "type": "Join",
119//! 	"data": 0 // room id
120//! },
121//! "client_token": ""
122//! }
123//! ```
124//! - `Action` - perform one of actions defined in your `Reducer`
125//! example:
126//! ```json
127//! {
128//! "message": {
129//!     "type": "Action",
130//! 	"data": "{\"type\":\"ActionIdentifyUser\",\"data\":\"quasarity\"}" // action payload
131//!  },
132//!  "client_token": ""
133//!  }
134//! ```
135//! - `Leave` - leave current room
136//! example:
137//! ```json
138//! {
139//! "message": {
140//!     "type": "Jeave",
141//! },
142//! "client_token": ""
143//! }
144//! ```
145//!
146//! #### And server will respond with one of four types of messages:
147//! - `RoomCreated` - room created successfully
148//! example:
149//! ```json
150//! {
151//! "status": "RoomCreated",
152//! "message": 0 // room id
153//! }
154//! ```
155//! - `RoomJoined` - joined existing room successfully
156//! example:
157//! ```json
158//! {
159//! "status": "RoomJoined",
160//! "message": 0 // client id
161//! }
162//! ```
163//! - `StateSent` - state sent successfully, sent to each client individually
164//! example:
165//! ```json
166//! {
167//! "status": "StateSent",
168//! "message": "{
169//!        "users": {
170//!            "0": "quasarity"
171//!        },
172//!        "messages": [
173//!             {
174//!                 "author": 0,
175//!                 "content": "Hello, world!"
176//!             }
177//!         ]}"
178//!     }
179//! }
180//! ```
181//! - `Action` - action performed successfully, sent to each client in room
182//! example:
183//! ```json
184//! {
185//! 	"status": "Action",
186//! 	"message": {
187//! 		"author": 0,
188//! 		"data": "quasarity",
189//! 		"state": {
190//! 			"messages": [],
191//! 			"users": {
192//! 				"0": "quasarity",
193//! 			}
194//! 		},
195//! 		"status": "ActionIdentifyUser"
196//! 	}
197//! }
198//! ```
199//! - `RoomLeft` - left current room successfully
200//! example:
201//! ```json
202//! {
203//! "status": "RoomLeft",
204//! "message": 0 // client id
205//! }
206//!
207/// Broadcaster is core structure responsible handling room-split communication and multiple reducers.
208mod broadcaster;
209
210/// Client is a structure that represents a client connected to the server.
211mod client;
212
213/// Connection is a structure that represents a connection to a client.
214pub mod connection;
215
216/// Dispatcher is a structure that handles incoming messages and dispatches them to the appropriate reducer.
217pub mod dispatcher;
218
219/// Joint is a structure that represents a joint implementation for real-time communication.
220pub mod joint;
221
222/// Message is a structure that represents a message sent over the WebSocket connection.
223pub mod message;
224
225/// Response is a structure that represents a response sent back to the client.
226pub mod response;
227
228/// Room is a structure that represents a room in which clients can join and communicate.
229mod room;
230
231/// State is a structure that represents the state of the application.
232pub mod utils;
233
234/// This module contains the code generation macros for injoint.
235pub mod codegen {
236    /// This module contains the code generation macros for injoint.
237    pub use injoint_macros::*;
238}