/// An enum representing the various forms of a WebSocket message. #[derive(Debug, Eq, PartialEq, Clone)] pub enum Message { /// A text WebSocket message Text(String), /// A binary WebSocket message Binary(Vec), /// A close message with the optional close frame. Close(Option>), Ping(Vec), Pong(Vec), } impl Message { /// Create a new text WebSocket message from a stringable. pub fn text(string: S) -> Message where S: Into, { Message::Text(string.into()) } /// Create a new binary WebSocket message by converting to Vec. pub fn binary(bin: B) -> Message where B: Into>, { Message::Binary(bin.into()) } /// Indicates whether a message is a text message. pub fn is_text(&self) -> bool { matches!(*self, Message::Text(_)) } /// Indicates whether a message is a binary message. pub fn is_binary(&self) -> bool { matches!(*self, Message::Binary(_)) } /// Indicates whether a message is a ping message. pub fn is_ping(&self) -> bool { matches!(self, Message::Ping(_)) } /// Indicates whether a message is a pong message. pub fn is_pong(&self) -> bool { matches!(self, Message::Pong(_)) } /// Indicates whether a message ia s close message. pub fn is_close(&self) -> bool { matches!(*self, Message::Close(_)) } /// Get the length of the WebSocket message. pub fn len(&self) -> usize { match self { Message::Text(s) => s.len(), Message::Binary(data) => data.len(), Message::Close(data) => data.as_ref().map(|d| d.reason.len()).unwrap_or(0), Message::Ping(data) => data.len(), Message::Pong(data) => data.len(), } } /// Returns true if the WebSocket message has no content. /// For example, if the other side of the connection sent an empty string. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Consume the WebSocket and return it as binary data. pub fn into_data(self) -> Vec { match self { Message::Text(string) => string.into_bytes(), Message::Binary(data) => data, Message::Close(None) => Vec::new(), Message::Close(Some(frame)) => frame.reason.into_owned().into_bytes(), Message::Ping(data) => data, Message::Pong(data) => data, } } /// Attempt to consume the WebSocket message and convert it to a String. pub fn into_text(self) -> Result { match self { Message::Text(string) => Ok(string), Message::Binary(data) => Ok(String::from_utf8(data).map_err(|err| err.utf8_error())?), Message::Close(None) => Ok(String::new()), Message::Close(Some(frame)) => Ok(frame.reason.into_owned()), Message::Ping(data) => Ok(String::from_utf8(data).map_err(|err| err.utf8_error())?), Message::Pong(data) => Ok(String::from_utf8(data).map_err(|err| err.utf8_error())?), } } /// Attempt to get a &str from the WebSocket message, /// this will try to convert binary data to utf8. pub fn to_text(&self) -> Result<&str, crate::Error> { match self { Message::Text(s) => Ok(s.as_str()), Message::Binary(data) => Ok(std::str::from_utf8(data)?), Message::Close(None) => Ok(""), Message::Close(Some(ref frame)) => Ok(&frame.reason), Message::Ping(data) => Ok(std::str::from_utf8(data)?), Message::Pong(data) => Ok(std::str::from_utf8(data)?), } } } impl From for Message { fn from(string: String) -> Self { Message::text(string) } } impl<'s> From<&'s str> for Message { fn from(string: &'s str) -> Self { Message::text(string) } } impl<'b> From<&'b [u8]> for Message { fn from(data: &'b [u8]) -> Self { Message::binary(data) } } impl From> for Message { fn from(data: Vec) -> Self { Message::binary(data) } } impl From for Vec { fn from(message: Message) -> Self { message.into_data() } } impl std::convert::TryFrom for String { type Error = crate::Error; fn try_from(value: Message) -> std::result::Result { value.into_text() } } impl std::fmt::Display for Message { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { if let Ok(string) = self.to_text() { write!(f, "{}", string) } else { write!(f, "Binary Data", self.len()) } } } /// A struct representing the close command. #[derive(Debug, Clone, Eq, PartialEq)] pub struct CloseFrame<'t> { /// The reason as a code. pub code: coding::CloseCode, /// The reason as text string. pub reason: std::borrow::Cow<'t, str>, } impl<'t> CloseFrame<'t> { /// Convert into a owned string. pub fn into_owned(self) -> CloseFrame<'static> { CloseFrame { code: self.code, reason: self.reason.into_owned().into(), } } } impl<'t> std::fmt::Display for CloseFrame<'t> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{} ({})", self.reason, self.code) } } pub mod coding { use self::CloseCode::*; /// Status code used to indicate why an endpoint is closing the WebSocket connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum CloseCode { /// Indicates a normal closure, meaning that the purpose for /// which the connection was established has been fulfilled. Normal, /// Indicates that an endpoint is "going away", such as a server /// going down or a browser having navigated away from a page. Away, /// Indicates that an endpoint is terminating the connection due /// to a protocol error. Protocol, /// Indicates that an endpoint is terminating the connection /// because it has received a type of data it cannot accept (e.g., an /// endpoint that understands only text data MAY send this if it /// receives a binary message). Unsupported, /// Indicates that no status code was included in a closing frame. This /// close code makes it possible to use a single method, `on_close` to /// handle even cases where no close code was provided. Status, /// Indicates an abnormal closure. If the abnormal closure was due to an /// error, this close code will not be used. Instead, the `on_error` method /// of the handler will be called with the error. However, if the connection /// is simply dropped, without an error, this close code will be sent to the /// handler. Abnormal, /// Indicates that an endpoint is terminating the connection /// because it has received data within a message that was not /// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\] /// data within a text message). Invalid, /// Indicates that an endpoint is terminating the connection /// because it has received a message that violates its policy. This /// is a generic status code that can be returned when there is no /// other more suitable status code (e.g., Unsupported or Size) or if there /// is a need to hide specific details about the policy. Policy, /// Indicates that an endpoint is terminating the connection /// because it has received a message that is too big for it to /// process. Size, /// Indicates that an endpoint (client) is terminating the /// connection because it has expected the server to negotiate one or /// more extension, but the server didn't return them in the response /// message of the WebSocket handshake. The list of extensions that /// are needed should be given as the reason for closing. /// Note that this status code is not used by the server, because it /// can fail the WebSocket handshake instead. Extension, /// Indicates that a server is terminating the connection because /// it encountered an unexpected condition that prevented it from /// fulfilling the request. Error, /// Indicates that the server is restarting. A client may choose to reconnect, /// and if it does, it should use a randomized delay of 5-30 seconds between attempts. Restart, /// Indicates that the server is overloaded and the client should either connect /// to a different IP (when multiple targets exist), or reconnect to the same IP /// when a user has performed an action. Again, #[doc(hidden)] Tls, #[doc(hidden)] Reserved(u16), #[doc(hidden)] Iana(u16), #[doc(hidden)] Library(u16), #[doc(hidden)] Bad(u16), } impl CloseCode { /// Check if this CloseCode is allowed. pub fn is_allowed(self) -> bool { !matches!(self, Bad(_) | Reserved(_) | Status | Abnormal | Tls) } } impl std::fmt::Display for CloseCode { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let code: u16 = self.into(); write!(f, "{}", code) } } impl From for u16 { fn from(code: CloseCode) -> u16 { match code { Normal => 1000, Away => 1001, Protocol => 1002, Unsupported => 1003, Status => 1005, Abnormal => 1006, Invalid => 1007, Policy => 1008, Size => 1009, Extension => 1010, Error => 1011, Restart => 1012, Again => 1013, Tls => 1015, Reserved(code) => code, Iana(code) => code, Library(code) => code, Bad(code) => code, } } } impl<'t> From<&'t CloseCode> for u16 { fn from(code: &'t CloseCode) -> u16 { (*code).into() } } impl From for CloseCode { fn from(code: u16) -> CloseCode { match code { 1000 => Normal, 1001 => Away, 1002 => Protocol, 1003 => Unsupported, 1005 => Status, 1006 => Abnormal, 1007 => Invalid, 1008 => Policy, 1009 => Size, 1010 => Extension, 1011 => Error, 1012 => Restart, 1013 => Again, 1015 => Tls, 1..=999 => Bad(code), 1016..=2999 => Reserved(code), 3000..=3999 => Iana(code), 4000..=4999 => Library(code), _ => Bad(code), } } } }