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}