wsforge_core/
message.rs

1//! WebSocket message types and utilities.
2//!
3//! This module provides the core message abstraction used throughout WsForge.
4//! It wraps the underlying WebSocket message types from `tokio-tungstenite` and
5//! provides convenient methods for creating, inspecting, and parsing messages.
6//!
7//! # Overview
8//!
9//! WebSocket messages can be one of several types:
10//! - **Text**: UTF-8 encoded string data
11//! - **Binary**: Raw byte data
12//! - **Ping/Pong**: Control frames for keep-alive
13//! - **Close**: Connection termination frames
14//!
15//! The [`Message`] type provides a unified interface for working with all these types,
16//! with automatic conversion between WsForge's message type and the underlying
17//! `tungstenite` message type.
18//!
19//! # Message Types
20//!
21//! | Type | Description | Use Case |
22//! |------|-------------|----------|
23//! | [`MessageType::Text`] | UTF-8 text | JSON, commands, chat messages |
24//! | [`MessageType::Binary`] | Raw bytes | Images, files, protocol buffers |
25//! | [`MessageType::Ping`] | Keep-alive request | Connection health checks |
26//! | [`MessageType::Pong`] | Keep-alive response | Responding to pings |
27//! | [`MessageType::Close`] | Connection close | Graceful disconnection |
28//!
29//! # Examples
30//!
31//! ## Creating Messages
32//!
33//! ```
34//! use wsforge::prelude::*;
35//!
36//! // Text message
37//! let text_msg = Message::text("Hello, WebSocket!");
38//!
39//! // Binary message
40//! let binary_msg = Message::binary(vec![0x01, 0x02, 0x03]);
41//!
42//! // JSON message (text)
43//! let json_msg = Message::text(r#"{"type":"greeting","text":"hello"}"#);
44//! ```
45//!
46//! ## Inspecting Messages
47//!
48//! ```
49//! use wsforge::prelude::*;
50//!
51//! # fn example(msg: Message) {
52//! if msg.is_text() {
53//!     println!("Text: {}", msg.as_text().unwrap());
54//! } else if msg.is_binary() {
55//!     println!("Binary: {} bytes", msg.as_bytes().len());
56//! }
57//!
58//! // Get the message type
59//! match msg.message_type() {
60//!     MessageType::Text => println!("Received text"),
61//!     MessageType::Binary => println!("Received binary"),
62//!     _ => println!("Received control frame"),
63//! }
64//! # }
65//! ```
66//!
67//! ## Parsing JSON
68//!
69//! ```
70//! use wsforge::prelude::*;
71//! use serde::Deserialize;
72//!
73//! #[derive(Deserialize)]
74//! struct ChatMessage {
75//!     username: String,
76//!     text: String,
77//! }
78//!
79//! # fn example(msg: Message) -> Result<()> {
80//! // Parse JSON from message
81//! let chat: ChatMessage = msg.json()?;
82//! println!("{} says: {}", chat.username, chat.text);
83//! # Ok(())
84//! # }
85//! ```
86
87use crate::error::Result;
88use serde::de::DeserializeOwned;
89use tokio_tungstenite::tungstenite::Message as TungsteniteMessage;
90
91/// Represents the type of a WebSocket message.
92///
93/// This enum categorizes messages into their protocol-defined types.
94/// Most application logic will work with text and binary messages,
95/// while control frames (Ping, Pong, Close) are typically handled
96/// by the framework automatically.
97///
98/// # Examples
99///
100/// ```
101/// use wsforge::prelude::*;
102///
103/// # fn example(msg: Message) {
104/// match msg.message_type() {
105///     MessageType::Text => {
106///         println!("Processing text message");
107///     }
108///     MessageType::Binary => {
109///         println!("Processing binary data");
110///     }
111///     MessageType::Ping => {
112///         println!("Received ping (will auto-respond with pong)");
113///     }
114///     MessageType::Pong => {
115///         println!("Received pong");
116///     }
117///     MessageType::Close => {
118///         println!("Client disconnecting");
119///     }
120/// }
121/// # }
122/// ```
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum MessageType {
125    /// Text message containing UTF-8 encoded string data.
126    ///
127    /// This is the most common message type for:
128    /// - JSON data
129    /// - Plain text chat messages
130    /// - Commands and instructions
131    /// - XML or other text-based formats
132    Text,
133
134    /// Binary message containing raw byte data.
135    ///
136    /// Use this for:
137    /// - Images, audio, video files
138    /// - Protocol buffers, MessagePack
139    /// - Custom binary protocols
140    /// - Large data transfers
141    Binary,
142
143    /// Ping frame for connection keep-alive.
144    ///
145    /// Servers send pings to check if the connection is still active.
146    /// Clients should respond with a Pong frame (handled automatically).
147    Ping,
148
149    /// Pong frame responding to a Ping.
150    ///
151    /// This is automatically sent in response to Ping frames.
152    /// You rarely need to create these manually.
153    Pong,
154
155    /// Close frame indicating connection termination.
156    ///
157    /// Sent when either side wants to close the connection gracefully.
158    /// Contains optional close code and reason.
159    Close,
160}
161
162/// A WebSocket message.
163///
164/// This is the main type for working with WebSocket messages in WsForge.
165/// It wraps the raw message data and provides convenient methods for
166/// creating, inspecting, and converting messages.
167///
168/// # Thread Safety
169///
170/// `Message` is cheaply cloneable and can be safely shared across threads.
171///
172/// # Examples
173///
174/// ## Creating Messages
175///
176/// ```
177/// use wsforge::prelude::*;
178///
179/// // Create text message
180/// let greeting = Message::text("Hello!");
181///
182/// // Create binary message
183/// let data = Message::binary(vec!);[1][2][3][4]
184/// ```
185///
186/// ## Working with Content
187///
188/// ```
189/// use wsforge::prelude::*;
190///
191/// # fn example(msg: Message) {
192/// // Check message type
193/// if msg.is_text() {
194///     // Get text content
195///     if let Some(text) = msg.as_text() {
196///         println!("Message: {}", text);
197///     }
198/// }
199///
200/// // Get raw bytes (works for both text and binary)
201/// let bytes = msg.as_bytes();
202/// println!("Size: {} bytes", bytes.len());
203/// # }
204/// ```
205#[derive(Debug, Clone)]
206pub struct Message {
207    /// The raw message data as bytes.
208    ///
209    /// For text messages, this contains UTF-8 encoded text.
210    /// For binary messages, this contains raw bytes.
211    pub data: Vec<u8>,
212
213    /// The type of this message.
214    pub msg_type: MessageType,
215}
216
217impl Message {
218    /// Creates a new text message.
219    ///
220    /// The string is converted to UTF-8 bytes and stored as a text message.
221    ///
222    /// # Arguments
223    ///
224    /// * `text` - The text content (any type convertible to `String`)
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use wsforge::prelude::*;
230    ///
231    /// // From &str
232    /// let msg1 = Message::text("Hello");
233    ///
234    /// // From String
235    /// let msg2 = Message::text(String::from("World"));
236    ///
237    /// // From format!
238    /// let msg3 = Message::text(format!("User {} joined", 42));
239    /// ```
240    pub fn text(text: impl Into<String>) -> Self {
241        let string = text.into();
242        Self {
243            data: string.into_bytes(),
244            msg_type: MessageType::Text,
245        }
246    }
247
248    /// Creates a new binary message.
249    ///
250    /// The bytes are stored as-is without any encoding or processing.
251    ///
252    /// # Arguments
253    ///
254    /// * `data` - The binary data
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// use wsforge::prelude::*;
260    ///
261    /// // From Vec<u8>
262    /// let msg1 = Message::binary(vec![0x01, 0x02, 0x03]);
263    ///
264    /// // From byte slice
265    /// let bytes: &[u8] = &[0xFF, 0xFE, 0xFD];
266    /// let msg2 = Message::binary(bytes.to_vec());
267    ///
268    /// // From array
269    /// let msg3 = Message::binary(.to_vec());[2][3][4][1]
270    /// ```
271    pub fn binary(data: Vec<u8>) -> Self {
272        Self {
273            data,
274            msg_type: MessageType::Binary,
275        }
276    }
277
278    /// Creates a ping message.
279    ///
280    /// Ping messages are used for connection keep-alive. The client
281    /// should respond with a pong message (handled automatically by
282    /// most WebSocket implementations).
283    ///
284    /// # Arguments
285    ///
286    /// * `data` - Optional payload data (usually empty)
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use wsforge::prelude::*;
292    ///
293    /// // Simple ping
294    /// let ping = Message::ping(vec![]);
295    ///
296    /// // Ping with payload
297    /// let ping_with_data = Message::ping(b"timestamp:1234567890".to_vec());
298    /// ```
299    pub fn ping(data: Vec<u8>) -> Self {
300        Self {
301            data,
302            msg_type: MessageType::Ping,
303        }
304    }
305
306    /// Creates a pong message.
307    ///
308    /// Pong messages are sent in response to ping messages. This is
309    /// typically handled automatically by the framework.
310    ///
311    /// # Arguments
312    ///
313    /// * `data` - Payload data (should match the ping payload)
314    ///
315    /// # Examples
316    ///
317    /// ```
318    /// use wsforge::prelude::*;
319    ///
320    /// # fn example(ping_msg: Message) {
321    /// // Respond to a ping
322    /// if ping_msg.is_ping() {
323    ///     let pong = Message::pong(ping_msg.data.clone());
324    ///     // Send pong back...
325    /// }
326    /// # }
327    /// ```
328    pub fn pong(data: Vec<u8>) -> Self {
329        Self {
330            data,
331            msg_type: MessageType::Pong,
332        }
333    }
334
335    /// Creates a close message.
336    ///
337    /// Close messages signal graceful connection termination.
338    /// The connection will close after this message is sent/received.
339    ///
340    /// # Examples
341    ///
342    /// ```
343    /// use wsforge::prelude::*;
344    ///
345    /// // Simple close
346    /// let close = Message::close();
347    /// ```
348    pub fn close() -> Self {
349        Self {
350            data: Vec::new(),
351            msg_type: MessageType::Close,
352        }
353    }
354
355    /// Converts this message to a `tungstenite` message.
356    ///
357    /// This is used internally by the framework to convert between
358    /// WsForge's message type and the underlying WebSocket library.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// use wsforge::prelude::*;
364    ///
365    /// # fn example() {
366    /// let msg = Message::text("Hello");
367    /// let tungstenite_msg = msg.into_tungstenite();
368    /// # }
369    /// ```
370    pub fn into_tungstenite(self) -> TungsteniteMessage {
371        match self.msg_type {
372            MessageType::Text => {
373                TungsteniteMessage::Text(String::from_utf8_lossy(&self.data).to_string())
374            }
375            MessageType::Binary => TungsteniteMessage::Binary(self.data),
376            MessageType::Ping => TungsteniteMessage::Ping(self.data),
377            MessageType::Pong => TungsteniteMessage::Pong(self.data),
378            MessageType::Close => TungsteniteMessage::Close(None),
379        }
380    }
381
382    /// Creates a message from a `tungstenite` message.
383    ///
384    /// This is used internally by the framework to convert incoming
385    /// WebSocket messages to WsForge's message type.
386    ///
387    /// # Arguments
388    ///
389    /// * `msg` - A tungstenite WebSocket message
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// use wsforge::prelude::*;
395    /// use tokio_tungstenite::tungstenite::Message as TungsteniteMessage;
396    ///
397    /// # fn example() {
398    /// let tung_msg = TungsteniteMessage::Text("Hello".to_string());
399    /// let msg = Message::from_tungstenite(tung_msg);
400    /// assert!(msg.is_text());
401    /// # }
402    /// ```
403    pub fn from_tungstenite(msg: TungsteniteMessage) -> Self {
404        match msg {
405            TungsteniteMessage::Text(text) => Self::text(text),
406            TungsteniteMessage::Binary(data) => Self::binary(data),
407            TungsteniteMessage::Ping(data) => Self::ping(data),
408            TungsteniteMessage::Pong(data) => Self::pong(data),
409            TungsteniteMessage::Close(_) => Self::close(),
410            TungsteniteMessage::Frame(_) => Self::binary(vec![]),
411        }
412    }
413
414    /// Returns the type of this message.
415    ///
416    /// # Examples
417    ///
418    /// ```
419    /// use wsforge::prelude::*;
420    ///
421    /// # fn example() {
422    /// let msg = Message::text("Hello");
423    /// assert_eq!(msg.message_type(), MessageType::Text);
424    ///
425    /// let binary = Message::binary(vec!);[3][1][2]
426    /// assert_eq!(binary.message_type(), MessageType::Binary);
427    /// # }
428    /// ```
429    pub fn message_type(&self) -> MessageType {
430        self.msg_type
431    }
432
433    /// Checks if this is a text message.
434    ///
435    /// # Examples
436    ///
437    /// ```
438    /// use wsforge::prelude::*;
439    ///
440    /// # fn example(msg: Message) {
441    /// if msg.is_text() {
442    ///     println!("Processing text: {}", msg.as_text().unwrap());
443    /// }
444    /// # }
445    /// ```
446    pub fn is_text(&self) -> bool {
447        self.msg_type == MessageType::Text
448    }
449
450    /// Checks if this is a binary message.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use wsforge::prelude::*;
456    ///
457    /// # fn example(msg: Message) {
458    /// if msg.is_binary() {
459    ///     let bytes = msg.as_bytes();
460    ///     println!("Processing {} bytes of binary data", bytes.len());
461    /// }
462    /// # }
463    /// ```
464    pub fn is_binary(&self) -> bool {
465        self.msg_type == MessageType::Binary
466    }
467
468    /// Checks if this is a ping message.
469    ///
470    /// # Examples
471    ///
472    /// ```
473    /// use wsforge::prelude::*;
474    ///
475    /// # fn example(msg: Message) {
476    /// if msg.is_ping() {
477    ///     // Framework usually auto-responds with pong
478    ///     println!("Received ping");
479    /// }
480    /// # }
481    /// ```
482    pub fn is_ping(&self) -> bool {
483        self.msg_type == MessageType::Ping
484    }
485
486    /// Checks if this is a pong message.
487    ///
488    /// # Examples
489    ///
490    /// ```
491    /// use wsforge::prelude::*;
492    ///
493    /// # fn example(msg: Message) {
494    /// if msg.is_pong() {
495    ///     println!("Connection is alive");
496    /// }
497    /// # }
498    /// ```
499    pub fn is_pong(&self) -> bool {
500        self.msg_type == MessageType::Pong
501    }
502
503    /// Checks if this is a close message.
504    ///
505    /// # Examples
506    ///
507    /// ```
508    /// use wsforge::prelude::*;
509    ///
510    /// # fn example(msg: Message) {
511    /// if msg.is_close() {
512    ///     println!("Client is disconnecting");
513    /// }
514    /// # }
515    /// ```
516    pub fn is_close(&self) -> bool {
517        self.msg_type == MessageType::Close
518    }
519
520    /// Returns the message content as a string slice, if it's a text message.
521    ///
522    /// Returns `None` if the message is not text or if the data is not valid UTF-8.
523    ///
524    /// # Examples
525    ///
526    /// ```
527    /// use wsforge::prelude::*;
528    ///
529    /// # fn example(msg: Message) {
530    /// if let Some(text) = msg.as_text() {
531    ///     println!("Received: {}", text);
532    /// }
533    /// # }
534    /// ```
535    pub fn as_text(&self) -> Option<&str> {
536        if self.is_text() {
537            std::str::from_utf8(&self.data).ok()
538        } else {
539            None
540        }
541    }
542
543    /// Returns the message content as a byte slice.
544    ///
545    /// This works for all message types, returning the raw underlying data.
546    ///
547    /// # Examples
548    ///
549    /// ```
550    /// use wsforge::prelude::*;
551    ///
552    /// # fn example(msg: Message) {
553    /// let bytes = msg.as_bytes();
554    /// println!("Message size: {} bytes", bytes.len());
555    ///
556    /// // Works for text messages too
557    /// let text_msg = Message::text("Hello");
558    /// assert_eq!(text_msg.as_bytes(), b"Hello");
559    /// # }
560    /// ```
561    pub fn as_bytes(&self) -> &[u8] {
562        &self.data
563    }
564
565    /// Deserializes the message content as JSON.
566    ///
567    /// This is a convenience method for parsing JSON from text messages.
568    /// The type must implement `serde::Deserialize`.
569    ///
570    /// # Errors
571    ///
572    /// Returns an error if:
573    /// - The message is not text
574    /// - The JSON is malformed
575    /// - The JSON doesn't match the expected type
576    ///
577    /// # Examples
578    ///
579    /// ## Simple Types
580    ///
581    /// ```
582    /// use wsforge::prelude::*;
583    ///
584    /// # fn example() -> Result<()> {
585    /// let msg = Message::text(r#"{"name":"Alice","age":30}"#);
586    ///
587    /// let value: serde_json::Value = msg.json()?;
588    /// assert_eq!(value["name"], "Alice");
589    /// # Ok(())
590    /// # }
591    /// ```
592    ///
593    /// ## Struct Deserialization
594    ///
595    /// ```
596    /// use wsforge::prelude::*;
597    /// use serde::Deserialize;
598    ///
599    /// #[derive(Deserialize)]
600    /// struct User {
601    ///     name: String,
602    ///     age: u32,
603    /// }
604    ///
605    /// # fn example() -> Result<()> {
606    /// let msg = Message::text(r#"{"name":"Alice","age":30}"#);
607    /// let user: User = msg.json()?;
608    /// assert_eq!(user.name, "Alice");
609    /// # Ok(())
610    /// # }
611    /// ```
612    pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
613        let text = self
614            .as_text()
615            .ok_or_else(|| crate::error::Error::InvalidMessage)?;
616        Ok(serde_json::from_str(text)?)
617    }
618}
619
620#[cfg(test)]
621mod tests {
622    use super::*;
623
624    #[test]
625    fn test_text_message() {
626        let msg = Message::text("Hello, World!");
627        assert!(msg.is_text());
628        assert_eq!(msg.as_text(), Some("Hello, World!"));
629        assert_eq!(msg.message_type(), MessageType::Text);
630    }
631
632    #[test]
633    fn test_binary_message() {
634        let data = vec![1, 2, 3, 4, 5];
635        let msg = Message::binary(data.clone());
636        assert!(msg.is_binary());
637        assert_eq!(msg.as_bytes(), &data[..]);
638        assert_eq!(msg.message_type(), MessageType::Binary);
639    }
640
641    #[test]
642    fn test_ping_message() {
643        let msg = Message::ping(vec![]);
644        assert!(msg.is_ping());
645        assert_eq!(msg.message_type(), MessageType::Ping);
646    }
647
648    #[test]
649    fn test_pong_message() {
650        let msg = Message::pong(vec![]);
651        assert!(msg.is_pong());
652        assert_eq!(msg.message_type(), MessageType::Pong);
653    }
654
655    #[test]
656    fn test_close_message() {
657        let msg = Message::close();
658        assert!(msg.is_close());
659        assert_eq!(msg.message_type(), MessageType::Close);
660    }
661
662    #[test]
663    fn test_json_parsing() {
664        let msg = Message::text(r#"{"key":"value","number":42}"#);
665        let json: serde_json::Value = msg.json().unwrap();
666        assert_eq!(json["key"], "value");
667        assert_eq!(json["number"], 42);
668    }
669
670    #[test]
671    fn test_as_bytes() {
672        let text_msg = Message::text("Hello");
673        assert_eq!(text_msg.as_bytes(), b"Hello");
674
675        let binary_msg = Message::binary(vec![1, 2, 3]);
676        assert_eq!(binary_msg.as_bytes(), &[1, 2, 3]);
677    }
678
679    #[test]
680    fn test_tungstenite_conversion() {
681        let msg = Message::text("test");
682        let tung_msg = msg.clone().into_tungstenite();
683        let back = Message::from_tungstenite(tung_msg);
684        assert_eq!(back.as_text(), msg.as_text());
685    }
686}