async_wsocket/
message.rs

1// Copyright (c) 2022-2024 Yuki Kishimoto
2// Distributed under the MIT software license
3
4use std::{fmt, str};
5
6#[cfg(not(target_arch = "wasm32"))]
7use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
8#[cfg(not(target_arch = "wasm32"))]
9use tokio_tungstenite::tungstenite::protocol::CloseFrame as TungsteniteCloseFrame;
10#[cfg(not(target_arch = "wasm32"))]
11use tokio_tungstenite::tungstenite::protocol::Message as TungsteniteMessage;
12
13#[cfg(not(target_arch = "wasm32"))]
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct CloseFrame {
16    /// The reason as a code.
17    pub code: u16,
18    /// The reason as text string.
19    pub reason: String,
20}
21
22/// An enum representing the various forms of a WebSocket message.
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub enum Message {
25    /// A text WebSocket message
26    Text(String),
27    /// A binary WebSocket message
28    Binary(Vec<u8>),
29    /// A ping message with the specified payload
30    ///
31    /// The payload here must have a length less than 125 bytes
32    #[cfg(not(target_arch = "wasm32"))]
33    Ping(Vec<u8>),
34    /// A pong message with the specified payload
35    ///
36    /// The payload here must have a length less than 125 bytes
37    #[cfg(not(target_arch = "wasm32"))]
38    Pong(Vec<u8>),
39    /// A close message with the optional close frame.
40    #[cfg(not(target_arch = "wasm32"))]
41    Close(Option<CloseFrame>),
42}
43
44impl Message {
45    #[cfg(not(target_arch = "wasm32"))]
46    pub(crate) fn from_native(msg: TungsteniteMessage) -> Self {
47        match msg {
48            TungsteniteMessage::Text(text) => Self::Text(text.to_string()),
49            TungsteniteMessage::Binary(data) => Self::Binary(data.to_vec()),
50            TungsteniteMessage::Ping(data) => Self::Ping(data.to_vec()),
51            TungsteniteMessage::Pong(data) => Self::Pong(data.to_vec()),
52            TungsteniteMessage::Close(frame) => Self::Close(frame.map(|f| f.into())),
53            // SAFETY: from tungstenite docs: "you're not going to get this value while reading the message".
54            // SAFETY: this conversion is used only in Stream trait, so when reading the messages.
55            TungsteniteMessage::Frame(..) => unreachable!(),
56        }
57    }
58
59    /// Get the length of the WebSocket message.
60    #[inline]
61    pub fn len(&self) -> usize {
62        match self {
63            Self::Text(string) => string.len(),
64            Self::Binary(data) => data.len(),
65            #[cfg(not(target_arch = "wasm32"))]
66            Self::Ping(data) => data.len(),
67            #[cfg(not(target_arch = "wasm32"))]
68            Self::Pong(data) => data.len(),
69            #[cfg(not(target_arch = "wasm32"))]
70            Self::Close(data) => data.as_ref().map(|d| d.reason.len()).unwrap_or(0),
71        }
72    }
73
74    #[inline]
75    pub fn is_empty(&self) -> bool {
76        self.len() == 0
77    }
78
79    /// Attempt to get a &str from the WebSocket message,
80    /// this will try to convert binary data to utf8.
81    pub fn as_text(&self) -> Option<&str> {
82        match self {
83            Self::Text(string) => Some(string.as_str()),
84            Self::Binary(data) => str::from_utf8(data).ok(),
85            #[cfg(not(target_arch = "wasm32"))]
86            Self::Ping(data) | Self::Pong(data) => str::from_utf8(data).ok(),
87            #[cfg(not(target_arch = "wasm32"))]
88            Self::Close(None) => Some(""),
89            #[cfg(not(target_arch = "wasm32"))]
90            Self::Close(Some(frame)) => Some(&frame.reason),
91        }
92    }
93}
94
95impl fmt::Display for Message {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        if let Some(string) = self.as_text() {
98            write!(f, "{string}")
99        } else {
100            write!(f, "Binary Data<length={}>", self.len())
101        }
102    }
103}
104
105#[cfg(not(target_arch = "wasm32"))]
106impl From<CloseFrame> for TungsteniteCloseFrame {
107    fn from(frame: CloseFrame) -> Self {
108        Self {
109            code: CloseCode::from(frame.code),
110            reason: frame.reason.into(),
111        }
112    }
113}
114
115#[cfg(not(target_arch = "wasm32"))]
116impl From<Message> for TungsteniteMessage {
117    fn from(msg: Message) -> Self {
118        match msg {
119            Message::Text(text) => Self::Text(text.into()),
120            Message::Binary(data) => Self::Binary(data.into()),
121            Message::Ping(data) => Self::Ping(data.into()),
122            Message::Pong(data) => Self::Pong(data.into()),
123            Message::Close(frame) => Self::Close(frame.map(|f| f.into())),
124        }
125    }
126}
127
128#[cfg(not(target_arch = "wasm32"))]
129impl From<TungsteniteCloseFrame> for CloseFrame {
130    fn from(frame: TungsteniteCloseFrame) -> Self {
131        Self {
132            code: frame.code.into(),
133            reason: frame.reason.to_string(),
134        }
135    }
136}