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