rusty_common/websocket/
message.rs1use smartstring::alias::String;
6use std::fmt;
7use yawc::close::CloseCode;
8use yawc::frame::{FrameView, OpCode};
9
10#[derive(Clone)]
17pub enum Message {
18 Text(String),
20
21 Binary(Vec<u8>),
23
24 Ping(Vec<u8>),
26
27 Pong(Vec<u8>),
29
30 Close(Option<(u16, String)>),
32
33 Frame(FrameView),
35}
36
37impl Message {
38 pub fn text<S: Into<String>>(text: S) -> Self {
40 Message::Text(text.into())
41 }
42
43 pub fn binary<V: Into<Vec<u8>>>(data: V) -> Self {
45 Message::Binary(data.into())
46 }
47
48 pub fn ping<V: Into<Vec<u8>>>(data: V) -> Self {
50 Message::Ping(data.into())
51 }
52
53 pub fn pong<V: Into<Vec<u8>>>(data: V) -> Self {
55 Message::Pong(data.into())
56 }
57
58 #[must_use]
60 pub fn close(code: u16, reason: &str) -> Self {
61 Message::Close(Some((code, reason.into())))
62 }
63
64 pub const fn is_text(&self) -> bool {
66 matches!(self, Message::Text(_))
67 }
68
69 pub const fn is_binary(&self) -> bool {
71 matches!(self, Message::Binary(_))
72 }
73
74 pub const fn is_ping(&self) -> bool {
76 matches!(self, Message::Ping(_))
77 }
78
79 pub const fn is_pong(&self) -> bool {
81 matches!(self, Message::Pong(_))
82 }
83
84 pub const fn is_close(&self) -> bool {
86 matches!(self, Message::Close(_))
87 }
88
89 pub const fn is_frame(&self) -> bool {
91 matches!(self, Message::Frame(_))
92 }
93
94 pub fn as_text(&self) -> Option<&str> {
96 match self {
97 Message::Text(text) => Some(text.as_str()),
98 _ => None,
99 }
100 }
101
102 pub fn as_binary(&self) -> Option<&[u8]> {
104 match self {
105 Message::Binary(data) => Some(data),
106 _ => None,
107 }
108 }
109
110 pub fn to_frame_view(self) -> FrameView {
112 match self {
113 Message::Text(text) => FrameView::text(text.to_string()),
114 Message::Binary(data) => FrameView::binary(data),
115 Message::Ping(data) => FrameView::ping(data),
116 Message::Pong(data) => FrameView::pong(data),
117 Message::Close(Some((code, reason))) => {
118 let close_code = CloseCode::Iana(code);
119 FrameView::close(close_code, reason.as_str())
120 }
121 Message::Close(None) => FrameView::close(CloseCode::Iana(1000), ""),
122 Message::Frame(frame) => frame,
123 }
124 }
125
126 #[must_use]
128 pub fn from_frame_view(frame: FrameView) -> Self {
129 match frame.opcode {
130 OpCode::Text => {
131 let text = std::str::from_utf8(&frame.payload)
132 .unwrap_or_default()
133 .into();
134 Message::Text(text)
135 }
136 OpCode::Binary => Message::Binary(frame.payload.to_vec()),
137 OpCode::Ping => Message::Ping(frame.payload.to_vec()),
138 OpCode::Pong => Message::Pong(frame.payload.to_vec()),
139 OpCode::Close => {
140 if frame.payload.len() >= 2 {
141 let code = u16::from_be_bytes([frame.payload[0], frame.payload[1]]);
142 let reason = if frame.payload.len() > 2 {
143 std::str::from_utf8(&frame.payload[2..])
144 .unwrap_or_default()
145 .into()
146 } else {
147 String::new()
148 };
149 Message::Close(Some((code, reason)))
150 } else {
151 Message::Close(None)
152 }
153 }
154 _ => Message::Binary(vec![]), }
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum MessageType {
162 Data,
164
165 Control,
167}
168
169impl Message {
170 pub const fn message_type(&self) -> MessageType {
172 match self {
173 Message::Text(_) | Message::Binary(_) => MessageType::Data,
174 Message::Ping(_) | Message::Pong(_) | Message::Close(_) | Message::Frame(_) => {
175 MessageType::Control
176 }
177 }
178 }
179}
180
181impl fmt::Debug for Message {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 match self {
184 Message::Text(text) => f.debug_tuple("Text").field(text).finish(),
185 Message::Binary(data) => f
186 .debug_tuple("Binary")
187 .field(&format!("{} bytes", data.len()))
188 .finish(),
189 Message::Ping(data) => f
190 .debug_tuple("Ping")
191 .field(&format!("{} bytes", data.len()))
192 .finish(),
193 Message::Pong(data) => f
194 .debug_tuple("Pong")
195 .field(&format!("{} bytes", data.len()))
196 .finish(),
197 Message::Close(frame) => f.debug_tuple("Close").field(frame).finish(),
198 Message::Frame(_) => f.debug_struct("Frame").finish_non_exhaustive(),
199 }
200 }
201}
202
203impl fmt::Display for Message {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 Message::Text(text) => write!(f, "Text message: {text}"),
207 Message::Binary(data) => write!(f, "Binary message: {} bytes", data.len()),
208 Message::Ping(data) => write!(f, "Ping message: {} bytes", data.len()),
209 Message::Pong(data) => write!(f, "Pong message: {} bytes", data.len()),
210 Message::Close(Some((code, reason))) => {
211 write!(f, "Close message: {code} - {reason}")
212 }
213 Message::Close(None) => write!(f, "Close message"),
214 Message::Frame(_) => write!(f, "Frame message"),
215 }
216 }
217}