1use super::error::ProtocolError;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CloseCode {
6 Normal,
8 GoingAway,
10 Protocol,
12 Unsupported,
14 NoStatus,
16 InvalidPayload,
18 PolicyViolation,
20 MessageTooBig,
22 MandatoryExtension,
24 InternalError,
26 Other(u16),
28}
29
30impl CloseCode {
31 pub fn from_u16(code: u16) -> Result<Self, ProtocolError> {
37 match code {
38 1000 => Ok(Self::Normal),
39 1001 => Ok(Self::GoingAway),
40 1002 => Ok(Self::Protocol),
41 1003 => Ok(Self::Unsupported),
42 1007 => Ok(Self::InvalidPayload),
44 1008 => Ok(Self::PolicyViolation),
45 1009 => Ok(Self::MessageTooBig),
46 1010 => Ok(Self::MandatoryExtension),
47 1011 => Ok(Self::InternalError),
48 3000..=4999 => Ok(Self::Other(code)),
49 _ => Err(ProtocolError::InvalidCloseCode(code)),
50 }
51 }
52
53 pub fn as_u16(&self) -> u16 {
55 match self {
56 Self::Normal => 1000,
57 Self::GoingAway => 1001,
58 Self::Protocol => 1002,
59 Self::Unsupported => 1003,
60 Self::NoStatus => 1005,
61 Self::InvalidPayload => 1007,
62 Self::PolicyViolation => 1008,
63 Self::MessageTooBig => 1009,
64 Self::MandatoryExtension => 1010,
65 Self::InternalError => 1011,
66 Self::Other(code) => *code,
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
73pub struct CloseFrame<'a> {
74 pub code: CloseCode,
76 pub reason: &'a str,
78}
79
80#[derive(Debug, Clone)]
82pub struct OwnedCloseFrame {
83 pub code: CloseCode,
85 pub reason: String,
87}
88
89#[derive(Debug, Clone)]
97pub enum Message<'a> {
98 Text(&'a str),
100 Binary(&'a [u8]),
102 Ping(&'a [u8]),
104 Pong(&'a [u8]),
106 Close(CloseFrame<'a>),
108}
109
110impl<'a> Message<'a> {
111 pub fn as_bytes(&self) -> &[u8] {
117 match self {
118 Self::Text(s) => s.as_bytes(),
119 Self::Binary(b) | Self::Ping(b) | Self::Pong(b) => b,
120 Self::Close(cf) => cf.reason.as_bytes(),
121 }
122 }
123
124 pub fn into_bytes(self) -> &'a [u8] {
129 match self {
130 Self::Text(s) => s.as_bytes(),
131 Self::Binary(b) | Self::Ping(b) | Self::Pong(b) => b,
132 Self::Close(cf) => cf.reason.as_bytes(),
133 }
134 }
135
136 pub fn into_owned(self) -> OwnedMessage {
138 match self {
139 Self::Text(s) => OwnedMessage::Text(s.to_owned()),
140 Self::Binary(b) => OwnedMessage::Binary(b.to_vec()),
141 Self::Ping(b) => OwnedMessage::Ping(b.to_vec()),
142 Self::Pong(b) => OwnedMessage::Pong(b.to_vec()),
143 Self::Close(cf) => OwnedMessage::Close(OwnedCloseFrame {
144 code: cf.code,
145 reason: cf.reason.to_owned(),
146 }),
147 }
148 }
149}
150
151#[derive(Debug, Clone)]
153pub enum OwnedMessage {
154 Text(String),
156 Binary(Vec<u8>),
158 Ping(Vec<u8>),
160 Pong(Vec<u8>),
162 Close(OwnedCloseFrame),
164}
165
166impl OwnedMessage {
167 pub fn as_bytes(&self) -> &[u8] {
173 match self {
174 Self::Text(s) => s.as_bytes(),
175 Self::Binary(b) | Self::Ping(b) | Self::Pong(b) => b,
176 Self::Close(cf) => cf.reason.as_bytes(),
177 }
178 }
179
180 #[cfg(feature = "bytes")]
189 pub fn to_bytes(self) -> bytes::Bytes {
190 match self {
191 Self::Text(s) => bytes::Bytes::from(s.into_bytes()),
192 Self::Binary(b) | Self::Ping(b) | Self::Pong(b) => bytes::Bytes::from(b),
193 Self::Close(cf) => bytes::Bytes::from(cf.reason.into_bytes()),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn close_code_round_trip() {
204 let codes = [
205 (1000, CloseCode::Normal),
206 (1001, CloseCode::GoingAway),
207 (1002, CloseCode::Protocol),
208 (1003, CloseCode::Unsupported),
209 (1007, CloseCode::InvalidPayload),
210 (1008, CloseCode::PolicyViolation),
211 (1009, CloseCode::MessageTooBig),
212 (1010, CloseCode::MandatoryExtension),
213 (1011, CloseCode::InternalError),
214 (3000, CloseCode::Other(3000)),
215 (4999, CloseCode::Other(4999)),
216 ];
217 for (raw, expected) in &codes {
218 let parsed = CloseCode::from_u16(*raw).unwrap();
219 assert_eq!(parsed, *expected);
220 assert_eq!(parsed.as_u16(), *raw);
221 }
222 }
223
224 #[test]
225 fn close_code_rejects_invalid() {
226 let invalid = [0, 999, 1004, 1005, 1006, 1015, 1016, 2999, 5000, u16::MAX];
227 for code in &invalid {
228 assert!(
229 CloseCode::from_u16(*code).is_err(),
230 "should reject code {code}"
231 );
232 }
233 }
234
235 #[test]
236 fn message_into_owned() {
237 let text = Message::Text("hello");
238 let owned = text.into_owned();
239 assert!(matches!(owned, OwnedMessage::Text(s) if s == "hello"));
240
241 let binary = Message::Binary(&[1, 2, 3]);
242 let owned = binary.into_owned();
243 assert!(matches!(owned, OwnedMessage::Binary(b) if b == vec![1, 2, 3]));
244
245 let close = Message::Close(CloseFrame {
246 code: CloseCode::Normal,
247 reason: "bye",
248 });
249 let owned = close.into_owned();
250 assert!(matches!(
251 owned,
252 OwnedMessage::Close(OwnedCloseFrame { code: CloseCode::Normal, reason }) if reason == "bye"
253 ));
254 }
255
256 #[test]
257 fn owned_message_as_bytes() {
258 assert_eq!(OwnedMessage::Text("hello".into()).as_bytes(), b"hello");
259 assert_eq!(OwnedMessage::Binary(vec![1, 2, 3]).as_bytes(), &[1, 2, 3]);
260 assert_eq!(OwnedMessage::Ping(vec![4, 5]).as_bytes(), &[4, 5]);
261 assert_eq!(OwnedMessage::Pong(vec![6]).as_bytes(), &[6]);
262 let close = OwnedMessage::Close(OwnedCloseFrame {
264 code: CloseCode::Normal,
265 reason: "bye".into(),
266 });
267 assert_eq!(close.as_bytes(), b"bye");
268 }
269
270 #[cfg(feature = "bytes")]
271 #[test]
272 fn owned_message_to_bytes() {
273 let text = OwnedMessage::Text("hello".into());
274 let b = text.to_bytes();
275 assert_eq!(&b[..], b"hello");
276
277 let binary = OwnedMessage::Binary(vec![1, 2, 3]);
278 let b = binary.to_bytes();
279 assert_eq!(&b[..], &[1, 2, 3]);
280
281 let ping = OwnedMessage::Ping(vec![4, 5]);
282 let b = ping.to_bytes();
283 assert_eq!(&b[..], &[4, 5]);
284
285 let close = OwnedMessage::Close(OwnedCloseFrame {
287 code: CloseCode::Normal,
288 reason: "bye".into(),
289 });
290 let b = close.to_bytes();
291 assert_eq!(&b[..], b"bye");
292 }
293}