rustapi_ws/
message.rs

1//! WebSocket message types
2
3use serde::{de::DeserializeOwned, Serialize};
4use std::borrow::Cow;
5
6/// WebSocket message type
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum Message {
9    /// Text message (UTF-8 encoded)
10    Text(String),
11    /// Binary message
12    Binary(Vec<u8>),
13    /// Ping message
14    Ping(Vec<u8>),
15    /// Pong message
16    Pong(Vec<u8>),
17    /// Close message
18    Close(Option<CloseFrame>),
19}
20
21impl Message {
22    /// Create a text message
23    pub fn text(text: impl Into<String>) -> Self {
24        Self::Text(text.into())
25    }
26
27    /// Create a binary message
28    pub fn binary(data: impl Into<Vec<u8>>) -> Self {
29        Self::Binary(data.into())
30    }
31
32    /// Create a ping message
33    pub fn ping(data: impl Into<Vec<u8>>) -> Self {
34        Self::Ping(data.into())
35    }
36
37    /// Create a pong message
38    pub fn pong(data: impl Into<Vec<u8>>) -> Self {
39        Self::Pong(data.into())
40    }
41
42    /// Create a close message
43    pub fn close() -> Self {
44        Self::Close(None)
45    }
46
47    /// Create a close message with a frame
48    pub fn close_with(code: CloseCode, reason: impl Into<String>) -> Self {
49        Self::Close(Some(CloseFrame {
50            code,
51            reason: Cow::Owned(reason.into()),
52        }))
53    }
54
55    /// Create a JSON text message from a serializable type
56    pub fn json<T: Serialize>(value: &T) -> Result<Self, crate::WebSocketError> {
57        serde_json::to_string(value)
58            .map(Self::Text)
59            .map_err(|e| crate::WebSocketError::serialization_error(e.to_string()))
60    }
61
62    /// Try to deserialize a text message as JSON
63    pub fn as_json<T: DeserializeOwned>(&self) -> Result<T, crate::WebSocketError> {
64        match self {
65            Self::Text(text) => serde_json::from_str(text)
66                .map_err(|e| crate::WebSocketError::deserialization_error(e.to_string())),
67            _ => Err(crate::WebSocketError::deserialization_error(
68                "Expected text message for JSON deserialization",
69            )),
70        }
71    }
72
73    /// Check if this is a text message
74    pub fn is_text(&self) -> bool {
75        matches!(self, Self::Text(_))
76    }
77
78    /// Check if this is a binary message
79    pub fn is_binary(&self) -> bool {
80        matches!(self, Self::Binary(_))
81    }
82
83    /// Check if this is a ping message
84    pub fn is_ping(&self) -> bool {
85        matches!(self, Self::Ping(_))
86    }
87
88    /// Check if this is a pong message
89    pub fn is_pong(&self) -> bool {
90        matches!(self, Self::Pong(_))
91    }
92
93    /// Check if this is a close message
94    pub fn is_close(&self) -> bool {
95        matches!(self, Self::Close(_))
96    }
97
98    /// Get the text content if this is a text message
99    pub fn as_text(&self) -> Option<&str> {
100        match self {
101            Self::Text(text) => Some(text),
102            _ => None,
103        }
104    }
105
106    /// Get the binary content if this is a binary message
107    pub fn as_bytes(&self) -> Option<&[u8]> {
108        match self {
109            Self::Binary(data) => Some(data),
110            _ => None,
111        }
112    }
113
114    /// Convert to text, consuming the message
115    pub fn into_text(self) -> Option<String> {
116        match self {
117            Self::Text(text) => Some(text),
118            _ => None,
119        }
120    }
121
122    /// Convert to bytes, consuming the message
123    pub fn into_bytes(self) -> Option<Vec<u8>> {
124        match self {
125            Self::Binary(data) => Some(data),
126            _ => None,
127        }
128    }
129}
130
131impl From<String> for Message {
132    fn from(text: String) -> Self {
133        Self::Text(text)
134    }
135}
136
137impl From<&str> for Message {
138    fn from(text: &str) -> Self {
139        Self::Text(text.to_string())
140    }
141}
142
143impl From<Vec<u8>> for Message {
144    fn from(data: Vec<u8>) -> Self {
145        Self::Binary(data)
146    }
147}
148
149impl From<&[u8]> for Message {
150    fn from(data: &[u8]) -> Self {
151        Self::Binary(data.to_vec())
152    }
153}
154
155/// Convert from tungstenite Message
156impl From<tungstenite::Message> for Message {
157    fn from(msg: tungstenite::Message) -> Self {
158        match msg {
159            tungstenite::Message::Text(text) => Self::Text(text.to_string()),
160            tungstenite::Message::Binary(data) => Self::Binary(data.to_vec()),
161            tungstenite::Message::Ping(data) => Self::Ping(data.to_vec()),
162            tungstenite::Message::Pong(data) => Self::Pong(data.to_vec()),
163            tungstenite::Message::Close(frame) => Self::Close(frame.map(|f| CloseFrame {
164                code: CloseCode::from(f.code),
165                reason: Cow::Owned(f.reason.to_string()),
166            })),
167            tungstenite::Message::Frame(_) => Self::Binary(vec![]), // Raw frames treated as binary
168        }
169    }
170}
171
172/// Convert to tungstenite Message
173impl From<Message> for tungstenite::Message {
174    fn from(msg: Message) -> Self {
175        match msg {
176            Message::Text(text) => tungstenite::Message::Text(text),
177            Message::Binary(data) => tungstenite::Message::Binary(data),
178            Message::Ping(data) => tungstenite::Message::Ping(data),
179            Message::Pong(data) => tungstenite::Message::Pong(data),
180            Message::Close(frame) => {
181                tungstenite::Message::Close(frame.map(|f| tungstenite::protocol::CloseFrame {
182                    code: f.code.into(),
183                    reason: f.reason,
184                }))
185            }
186        }
187    }
188}
189
190/// WebSocket close frame
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct CloseFrame {
193    /// Close code
194    pub code: CloseCode,
195    /// Close reason
196    pub reason: Cow<'static, str>,
197}
198
199impl CloseFrame {
200    /// Create a new close frame
201    pub fn new(code: CloseCode, reason: impl Into<Cow<'static, str>>) -> Self {
202        Self {
203            code,
204            reason: reason.into(),
205        }
206    }
207
208    /// Create a normal close frame
209    pub fn normal() -> Self {
210        Self::new(CloseCode::Normal, "")
211    }
212
213    /// Create a going away close frame
214    pub fn going_away() -> Self {
215        Self::new(CloseCode::Away, "Going away")
216    }
217}
218
219/// WebSocket close codes
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
221pub enum CloseCode {
222    /// Normal closure (1000)
223    Normal,
224    /// Going away (1001)
225    Away,
226    /// Protocol error (1002)
227    Protocol,
228    /// Unsupported data (1003)
229    Unsupported,
230    /// No status received (1005)
231    Status,
232    /// Abnormal closure (1006)
233    Abnormal,
234    /// Invalid frame payload data (1007)
235    Invalid,
236    /// Policy violation (1008)
237    Policy,
238    /// Message too big (1009)
239    Size,
240    /// Mandatory extension (1010)
241    Extension,
242    /// Internal error (1011)
243    Error,
244    /// Service restart (1012)
245    Restart,
246    /// Try again later (1013)
247    Again,
248    /// Bad TLS handshake (1015)
249    Tls,
250    /// Reserved codes
251    Reserved(u16),
252    /// Library/framework-specific codes (3000-3999)
253    Library(u16),
254    /// Private use codes (4000-4999)
255    Private(u16),
256}
257
258impl CloseCode {
259    /// Get the numeric code
260    pub fn as_u16(&self) -> u16 {
261        match self {
262            Self::Normal => 1000,
263            Self::Away => 1001,
264            Self::Protocol => 1002,
265            Self::Unsupported => 1003,
266            Self::Status => 1005,
267            Self::Abnormal => 1006,
268            Self::Invalid => 1007,
269            Self::Policy => 1008,
270            Self::Size => 1009,
271            Self::Extension => 1010,
272            Self::Error => 1011,
273            Self::Restart => 1012,
274            Self::Again => 1013,
275            Self::Tls => 1015,
276            Self::Reserved(code) => *code,
277            Self::Library(code) => *code,
278            Self::Private(code) => *code,
279        }
280    }
281}
282
283impl From<u16> for CloseCode {
284    fn from(code: u16) -> Self {
285        match code {
286            1000 => Self::Normal,
287            1001 => Self::Away,
288            1002 => Self::Protocol,
289            1003 => Self::Unsupported,
290            1005 => Self::Status,
291            1006 => Self::Abnormal,
292            1007 => Self::Invalid,
293            1008 => Self::Policy,
294            1009 => Self::Size,
295            1010 => Self::Extension,
296            1011 => Self::Error,
297            1012 => Self::Restart,
298            1013 => Self::Again,
299            1015 => Self::Tls,
300            1004 | 1014 | 1016..=2999 => Self::Reserved(code),
301            3000..=3999 => Self::Library(code),
302            4000..=4999 => Self::Private(code),
303            _ => Self::Reserved(code),
304        }
305    }
306}
307
308impl From<CloseCode> for u16 {
309    fn from(code: CloseCode) -> Self {
310        code.as_u16()
311    }
312}
313
314impl From<tungstenite::protocol::frame::coding::CloseCode> for CloseCode {
315    fn from(code: tungstenite::protocol::frame::coding::CloseCode) -> Self {
316        Self::from(u16::from(code))
317    }
318}
319
320impl From<CloseCode> for tungstenite::protocol::frame::coding::CloseCode {
321    fn from(code: CloseCode) -> Self {
322        tungstenite::protocol::frame::coding::CloseCode::from(code.as_u16())
323    }
324}