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}