diff options
| author | Sophie Forrest <git@sophieforrest.com> | 2024-08-30 23:13:20 +1200 |
|---|---|---|
| committer | Sophie Forrest <git@sophieforrest.com> | 2024-08-30 23:13:44 +1200 |
| commit | e3cb82a3b33bd2a2e49c58ce18d1258fb505869e (patch) | |
| tree | 2375279182fb4f90f5c28560a08cda90591f608b /crates/messenger_server/src/message.rs | |
Diffstat (limited to '')
| -rw-r--r-- | crates/messenger_server/src/message.rs | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/crates/messenger_server/src/message.rs b/crates/messenger_server/src/message.rs new file mode 100644 index 0000000..2682c1b --- /dev/null +++ b/crates/messenger_server/src/message.rs @@ -0,0 +1,103 @@ +//! Abstraction for the messaging system to increase overall type safety and +//! code readability. + +use std::sync::Arc; + +use axum::extract::ws::{Message, WebSocket}; +use futures::{stream::SplitSink, SinkExt}; +use messenger_common::{client::MessageType as ClientMessageType, server::MessageType}; +use tokio::sync::broadcast::Sender; +use tracing::error; + +use crate::app::State; + +/// Represents messages which can be sent through a [`WebSocket`]. +pub trait Server +where + Self: std::marker::Sized + serde::Serialize, +{ + /// Adds a message to the message history of the app state. + async fn append_to_history(&self, state: &Arc<State>) -> serde_json::Result<()>; + + /// Performs serialization of a message. + /// Semantic alternative to `serde_json::to_string()`. + fn serialize(&self) -> serde_json::Result<String> { + serde_json::to_string(&self) + } + + /// Sends a message through the provided sender. + /// + /// The main purpose of this function is to enforce type-safety when sending + /// a message. This prevents accidentally sending non-`MessageType` messages + /// through the server. + fn send(&self, tx: &Sender<String>); +} + +impl Server for MessageType { + /// Adds a message to the message history of the app state. + async fn append_to_history(&self, state: &Arc<State>) -> serde_json::Result<()> { + // Join and leave messages shouldn't be saved to history + // We ignore the inner message here as we want to serialize the entire message + if let Self::UserMessage(..) = *self { + let mut history_guard = state.message_history.lock().await; + + // Only save the last 100 messages + if history_guard.len() > 99 { + history_guard.pop_front(); + } + + // Add a new message to the back of the queue + history_guard.push_back(serde_json::to_string(self)?); + } + + Ok(()) + } + + fn serialize(&self) -> serde_json::Result<String> { + serde_json::to_string(&self) + } + + fn send(&self, tx: &Sender<String>) { + tracing::debug!("sending message, content: {:?}", &self); + + match self.serialize() { + Ok(json_message) => { + if let Err(error) = tx.send(json_message) { + error!("error occurred while sending a message through a channel: {error}"); + } + } + Err(error) => { + error!("error occurred while converting message to json: {error}"); + } + }; + } +} + +/// Performs deserialization of a message. +/// Semantic alternative to `serde_json::from_str::<MessageType>()`. +pub fn deserialize(message: &str) -> serde_json::Result<ClientMessageType> { + serde_json::from_str(message) +} + +/// Sends an error through a websocket to the client. +/// Contains error handling to reduce overall code bloat. +pub async fn send_error( + sender: &mut SplitSink<WebSocket, Message>, + error: messenger_common::server::Error, +) { + // Log error through tracing to show invalid client behaviour + error!("received message from client that is considered an error: {error}"); + + // Handle deserialization errors correctly to avoid a panic + match MessageType::Error(error).serialize() { + Ok(outbound_error) => { + if let Err(error) = sender.send(Message::Text(outbound_error)).await { + error!("unable to send error message through a websocket: {error}"); + } + } + Err(error) => { + // Errors can also occur during serialization, so these should be covered + error!("unable to serialize outbound error message: {error}"); + } + }; +} |