flubber_backend_proto/lib.rs
1//! The protocol between the server and backends.
2//!
3//! ## Example Session
4//!
5//! ```json
6//! P: {"backend_name": "Example", "backend_version": [0, 0, 1], "protocol_version": [0, 1, 0]}
7//! S: {"sequence_number": 0, "body": {"type": "RoomLookup", "value": "#general"}}
8//! P: {"sequence_number": 0, "body": {"type": "RoomID", "value": "#general"}}
9//! S: {"sequence_number": 1, "body": {"type": "RoomJoin", "value": "#general"}}
10//! P: {"sequence_number": 1, "body": {"type": "Success", "value": null}}
11//! S: {"sequence_number": 2, "body": {"type": "MessageSend", "value": {"recipient": "#general", "attachments": [], "content": {"type": "Text", "value: "Hello, world!"}, "extra": null}}}
12//! P: {"sequence_number": 2, "body": {"type": "MessageID", "value": "test"}}
13//! ```
14//!
15//! TODO:
16//! - Work out how emotes should work
17//! - EmoteID? Store emotes by hash?
18//! - Users
19#![deny(
20 bad_style,
21 bare_trait_objects,
22 const_err,
23 dead_code,
24 improper_ctypes,
25 legacy_directory_ownership,
26 missing_debug_implementations,
27 missing_docs,
28 no_mangle_generic_items,
29 non_shorthand_field_patterns,
30 overflowing_literals,
31 path_statements,
32 patterns_in_fns_without_body,
33 plugin_as_library,
34 private_in_public,
35 safe_extern_statics,
36 trivial_casts,
37 trivial_numeric_casts,
38 unconditional_recursion,
39 // unions_with_drop_fields,
40 unsafe_code,
41 unused,
42 unused_allocation,
43 unused_comparisons,
44 unused_extern_crates,
45 unused_import_braces,
46 unused_parens,
47 unused_qualifications,
48 unused_results,
49 while_true
50)]
51
52pub mod serde;
53
54use chrono::{DateTime, Utc};
55use mime::Mime;
56use serde_derive::{Deserialize, Serialize};
57use serde_json::Value as Json;
58use std::{
59 error::Error,
60 fmt::{Display, Formatter},
61};
62use sval::Value;
63
64/// Backend sends this to the server when it starts.
65#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
66#[sval(derive_from = "serde")]
67pub struct InitInfo {
68 /// The name of the backend.
69 pub backend_name: String,
70
71 /// The version of the backend.
72 pub backend_version: Version,
73
74 /// The version of the protocol. This is version `0.1.0`.
75 pub protocol_version: Version,
76}
77
78/// The version of the backend or protocol.
79#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
80#[sval(derive_from = "serde")]
81pub struct Version(pub u32, pub u32, pub u32);
82
83/// A name for a message on a service. This should uniquely identify the message, even if it gets edited.
84#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
85#[sval(derive_from = "serde")]
86pub struct MessageID(pub String);
87
88/// A name for a room on a service. This should uniquely identify a room through renames if possible.
89#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
90#[sval(derive_from = "serde")]
91pub struct RoomID(pub String);
92
93/// A name for a user on a service. This should uniquely identify a user through renames if possible.
94#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
95#[sval(derive_from = "serde")]
96pub struct UserID(pub String);
97
98/// A RoomID or UserID.
99#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
100#[serde(untagged)]
101#[sval(derive_from = "serde")]
102pub enum RoomIDOrUserID {
103 /// A RoomID.
104 Room(RoomID),
105
106 /// A UserID.
107 User(UserID),
108}
109
110/// A message sent from a user to another user or a room.
111#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
112#[sval(derive_from = "serde")]
113pub struct Message {
114 /// The ID of the message.
115 pub id: MessageID,
116
117 /// The sending User.
118 pub sender: UserID,
119
120 /// The Room sent to, or the User who was DM'd.
121 pub recipient: RoomIDOrUserID,
122
123 /// Attachments sent with the message.
124 pub attachments: Vec<MessageAttachment>,
125
126 /// The body of the message.
127 pub content: MessageContent,
128
129 /// The time the message was created.
130 #[serde(with = "crate::serde::unix_ms")]
131 pub create_time: DateTime<Utc>,
132
133 /// The time the message was last edited.
134 #[serde(with = "crate::serde::unix_ms")]
135 pub edit_time: DateTime<Utc>,
136
137 /// Extra backend-specific data.
138 #[serde(default)]
139 pub extra: Json,
140}
141
142/// A message sent from a user to another user or a room.
143#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
144#[sval(derive_from = "serde")]
145pub struct NewMessage {
146 /// The Room to send to, or the User to DM.
147 pub recipient: RoomIDOrUserID,
148
149 /// Attachments sent with the message.
150 pub attachments: Vec<MessageAttachment>,
151
152 /// The body of the message.
153 pub content: MessageContent,
154
155 /// Extra backend-specific data.
156 #[serde(default)]
157 pub extra: Json,
158}
159
160/// MIME-typed data attached to a message.
161#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
162#[sval(derive_from = "serde")]
163pub struct MessageAttachment {
164 /// The mime type of a message.
165 #[serde(with = "crate::serde::mime")]
166 pub mime: Mime,
167
168 /// The contents of the attachment.
169 #[serde(with = "crate::serde::base64")]
170 pub data: Vec<u8>,
171}
172
173/// The contents of a message.
174#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
175#[serde(tag = "type", content = "value")]
176#[sval(derive_from = "serde")]
177pub enum MessageContent {
178 /// Displays the enclosed content in bold.
179 Bold(Box<MessageContent>),
180
181 /// Concatenates the enclosed content.
182 Concat(Vec<MessageContent>),
183
184 /// Displays the enclosed content crossed out.
185 Crossout(Box<MessageContent>),
186
187 /// An emote.
188 ///
189 /// **TODO**: How to do these? Just make them cached images?
190 Emote(String),
191
192 /// Displays the enclosed content in italics.
193 Italic(Box<MessageContent>),
194
195 /// A link to a message.
196 MessageLink(MessageID),
197
198 /// A link to a room.
199 RoomLink(RoomID),
200
201 /// Plain text.
202 Text(String),
203
204 /// A link to a resource by URL.
205 UrlLink(String),
206
207 /// Displays the enclosed content with an underline.
208 Underline(Box<MessageContent>),
209
210 /// A link to a user.
211 UserLink(UserID),
212}
213
214/// The information corresponding to a room.
215#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
216#[sval(derive_from = "serde")]
217pub struct Room {
218 /// The ID of the room.
219 pub id: RoomID,
220
221 /// The room which is the parent of this room.
222 pub parent: Option<RoomID>,
223
224 /// The name of the room.
225 pub name: String,
226
227 /// Whether the room can be sent to.
228 pub sendable: bool,
229}
230
231/// A request to create a new room with the given properties.
232#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Value)]
233#[sval(derive_from = "serde")]
234pub struct NewRoom {
235 /// The room which is the parent of this room.
236 pub parent: Option<RoomID>,
237
238 /// The name of the room.
239 pub name: String,
240
241 /// Whether the room can be sent to.
242 pub sendable: bool,
243}
244
245/// Information sent from the backend to the server.
246#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
247#[serde(tag = "type", content = "value")]
248#[sval(derive_from = "serde")]
249pub enum Update {
250 /// Notification that a room was created or edited.
251 RoomUpsert(Room),
252
253 /// Notification that a room was deleted.
254 RoomDelete(RoomID),
255
256 /// Notification that a message was created or edited.
257 MessageUpsert(Message),
258
259 /// Notification that a message was deleted.
260 MessageDelete(MessageID),
261}
262
263/// A request as sent to the backend.
264#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
265#[sval(derive_from = "serde")]
266pub struct Request {
267 /// The sequence number of the request. Two requests with the same sequence number may not be
268 /// in flight at the same time.
269 pub sequence_number: u32,
270
271 /// The contents of the request.
272 pub body: RequestBody,
273}
274
275/// A response as sent from the backend.
276#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
277#[sval(derive_from = "serde")]
278pub struct Response {
279 /// The sequence number, which must match the sequence number in the request.
280 pub sequence_number: u32,
281
282 /// The contents of the response.
283 pub body: ResponseBody,
284}
285
286/// A Response or Update.
287#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
288#[serde(untagged)]
289#[sval(derive_from = "serde")]
290pub enum ResponseOrUpdate {
291 /// A Response.
292 Response(Response),
293
294 /// An Update.
295 Update(Update),
296}
297
298/// A request made to a server.
299#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
300#[serde(tag = "type", content = "value")]
301#[sval(derive_from = "serde")]
302pub enum RequestBody {
303 /// A request to get some number of messages earlier in history than the given one.
304 ///
305 /// The only valid non-error response is a `ResponseBody::Success`.
306 MessageGetBefore(MessageID),
307
308 /// A request to get information about a message by ID.
309 ///
310 /// The only valid non-error response is a `ResponseBody::Message`.
311 MessageGet(MessageID),
312
313 /// A request to send a message.
314 ///
315 /// The only valid non-error response is a `ResponseBody::MessageID`.
316 MessageSend(NewMessage),
317
318 /// A request to get information about a room by ID.
319 ///
320 /// The only valid non-error response is a `ResponseBody::Room`.
321 RoomGet(RoomID),
322
323 /// A request to create a room.
324 ///
325 /// The only valid non-error response is a `ResponseBody::RoomID`.
326 RoomCreate(NewRoom),
327
328 /// A request to get the ID of a named room.
329 ///
330 /// The only valid non-error response is a `ResponseBody::RoomID`.
331 RoomLookup(String),
332
333 /// A request to join a room.
334 ///
335 /// The only valid non-error response is a `ResponseBody::Success`.
336 RoomJoin(RoomID),
337
338 /// A request to leave a room.
339 ///
340 /// The only valid non-error response is a `ResponseBody::Success`.
341 RoomLeave(RoomID),
342}
343
344/// The response to a request.
345#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
346#[serde(tag = "type", content = "value")]
347#[sval(derive_from = "serde")]
348pub enum ResponseBody {
349 /// The request succeeded without returning a response.
350 Success,
351
352 /// The request succeeded, resulting in a message.
353 Message(Message),
354
355 /// The request succeeded, resulting in a room.
356 Room(Room),
357
358 /// The request succeeded, resulting in a message ID.
359 MessageID(MessageID),
360
361 /// The request succeeded, resulting in a room ID.
362 RoomID(RoomID),
363
364 /// The request failed.
365 Error(ResponseError),
366}
367
368/// An error with a request.
369#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Value)]
370#[sval(derive_from = "serde")]
371pub struct ResponseError {
372 /// An error message.
373 pub message: String,
374
375 /// Additional information to be logged for debugging, but not necessarily shown to the user.
376 #[serde(default)]
377 pub debug_info: Json,
378
379 /// Whether the error can be resolved by retrying the request. Note that this typically implies
380 /// that the action was idempotent; see https://www.tedinski.com/2019/02/20/idempotence.html.
381 #[serde(default)]
382 pub retry: bool,
383}
384
385impl Display for ResponseError {
386 fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> {
387 fmt.write_str(&self.message)
388 }
389}
390
391impl Error for ResponseError {
392 fn description(&self) -> &str {
393 &self.message
394 }
395}