use std::{fmt, ops::Deref};
use bytes::Bytes;
#[cfg(feature = "ws-fastwebsockets")]
use fastwebsockets::{Frame, OpCode, Payload};
use crate::Error;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Utf8Bytes(String);
impl Utf8Bytes {
#[inline]
pub fn from_static(str: &'static str) -> Self {
Self(str.to_string())
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Deref for Utf8Bytes {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl fmt::Display for Utf8Bytes {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl TryFrom<Bytes> for Utf8Bytes {
type Error = std::str::Utf8Error;
#[inline]
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
let s = std::str::from_utf8(&bytes)?.to_string();
Ok(Self(s))
}
}
impl TryFrom<Vec<u8>> for Utf8Bytes {
type Error = std::str::Utf8Error;
#[inline]
fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
let s = String::from_utf8(v).map_err(|e| e.utf8_error())?;
Ok(Self(s))
}
}
impl From<String> for Utf8Bytes {
#[inline]
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for Utf8Bytes {
#[inline]
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<&String> for Utf8Bytes {
#[inline]
fn from(s: &String) -> Self {
Self(s.clone())
}
}
impl From<Utf8Bytes> for Bytes {
#[inline]
fn from(val: Utf8Bytes) -> Self {
Bytes::from(val.0)
}
}
impl<T> PartialEq<T> for Utf8Bytes
where
for<'a> &'a str: PartialEq<T>,
{
#[inline]
fn eq(&self, other: &T) -> bool {
self.as_str() == *other
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CloseCode(pub(super) u16);
impl CloseCode {
pub const NORMAL: CloseCode = CloseCode(1000);
pub const AWAY: CloseCode = CloseCode(1001);
pub const PROTOCOL: CloseCode = CloseCode(1002);
pub const UNSUPPORTED: CloseCode = CloseCode(1003);
pub const STATUS: CloseCode = CloseCode(1005);
pub const ABNORMAL: CloseCode = CloseCode(1006);
pub const INVALID: CloseCode = CloseCode(1007);
pub const POLICY: CloseCode = CloseCode(1008);
pub const SIZE: CloseCode = CloseCode(1009);
pub const EXTENSION: CloseCode = CloseCode(1010);
pub const ERROR: CloseCode = CloseCode(1011);
pub const RESTART: CloseCode = CloseCode(1012);
pub const AGAIN: CloseCode = CloseCode(1013);
}
impl From<CloseCode> for u16 {
#[inline]
fn from(code: CloseCode) -> u16 {
code.0
}
}
impl From<u16> for CloseCode {
#[inline]
fn from(code: u16) -> CloseCode {
CloseCode(code)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CloseFrame {
pub code: CloseCode,
pub reason: Utf8Bytes,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Message {
Text(Utf8Bytes),
Binary(Bytes),
Ping(Bytes),
Pong(Bytes),
Close(Option<CloseFrame>),
}
impl Message {
#[cfg(feature = "ws-fastwebsockets")]
pub(crate) fn into_frame(self) -> Frame<'static> {
match self {
Self::Text(text) => Frame::text(Payload::Owned(text.0.into_bytes())),
Self::Binary(data) => Frame::binary(Payload::Owned(data.to_vec())),
Self::Ping(data) => Frame::new(true, OpCode::Ping, None, Payload::Owned(data.to_vec())),
Self::Pong(data) => Frame::new(true, OpCode::Pong, None, Payload::Owned(data.to_vec())),
Self::Close(Some(close)) => {
Frame::close(close.code.0, close.reason.as_str().as_bytes())
}
Self::Close(None) => Frame::new(true, OpCode::Close, None, Payload::Owned(vec![])),
}
}
#[cfg(feature = "ws-fastwebsockets")]
pub(crate) fn from_frame(frame: Frame) -> Self {
let data = Vec::from(frame.payload);
match frame.opcode {
OpCode::Text => {
let s = String::from_utf8_lossy(&data).to_string();
Self::Text(Utf8Bytes(s))
}
OpCode::Binary => Self::Binary(Bytes::from(data)),
OpCode::Ping => Self::Ping(Bytes::from(data)),
OpCode::Pong => Self::Pong(Bytes::from(data)),
OpCode::Close => {
if data.len() >= 2 {
let code = u16::from_be_bytes([data[0], data[1]]);
let reason = String::from_utf8_lossy(&data[2..]).to_string();
Self::Close(Some(CloseFrame {
code: CloseCode(code),
reason: Utf8Bytes(reason),
}))
} else {
Self::Close(None)
}
}
OpCode::Continuation => Self::Binary(Bytes::from(data)),
}
}
pub fn into_data(self) -> Bytes {
match self {
Self::Text(string) => Bytes::from(string),
Self::Binary(data) | Self::Ping(data) | Self::Pong(data) => data,
Self::Close(None) => Bytes::new(),
Self::Close(Some(frame)) => Bytes::from(frame.reason),
}
}
pub fn into_text(self) -> crate::Result<Utf8Bytes> {
match self {
Self::Text(string) => Ok(string),
Self::Binary(data) | Self::Ping(data) | Self::Pong(data) => {
Utf8Bytes::try_from(data).map_err(Error::decode)
}
Self::Close(None) => Ok(Utf8Bytes::default()),
Self::Close(Some(frame)) => Ok(frame.reason),
}
}
pub fn to_text(&self) -> crate::Result<&str> {
match *self {
Self::Text(ref string) => Ok(string.as_str()),
Self::Binary(ref data) | Self::Ping(ref data) | Self::Pong(ref data) => {
std::str::from_utf8(data).map_err(Error::decode)
}
Self::Close(None) => Ok(""),
Self::Close(Some(ref frame)) => Ok(&frame.reason),
}
}
}
impl Message {
pub fn text<S>(string: S) -> Message
where
S: Into<Utf8Bytes>,
{
Message::Text(string.into())
}
pub fn binary<B>(bin: B) -> Message
where
B: Into<Bytes>,
{
Message::Binary(bin.into())
}
pub fn ping<B>(bin: B) -> Message
where
B: Into<Bytes>,
{
Message::Ping(bin.into())
}
pub fn pong<B>(bin: B) -> Message
where
B: Into<Bytes>,
{
Message::Pong(bin.into())
}
pub fn close<C>(close: C) -> Message
where
C: Into<Option<CloseFrame>>,
{
Message::Close(close.into())
}
}
impl From<String> for Message {
fn from(string: String) -> Self {
Message::Text(string.into())
}
}
impl<'s> From<&'s str> for Message {
fn from(string: &'s str) -> Self {
Message::Text(string.into())
}
}
impl<'b> From<&'b [u8]> for Message {
fn from(data: &'b [u8]) -> Self {
Message::Binary(Bytes::copy_from_slice(data))
}
}
impl From<Vec<u8>> for Message {
fn from(data: Vec<u8>) -> Self {
Message::Binary(data.into())
}
}
impl From<Message> for Vec<u8> {
fn from(msg: Message) -> Self {
msg.into_data().to_vec()
}
}