use std::borrow::Borrow;
use std::fmt;
use std::ops::Deref;
use bytes::Bytes;
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Utf8Bytes(Bytes);
impl Utf8Bytes {
pub const fn from_static(value: &'static str) -> Self {
Self(Bytes::from_static(value.as_bytes()))
}
pub fn as_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(&self.0) }
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn into_bytes(self) -> Bytes {
self.0
}
#[cfg(any(feature = "ws", feature = "ws-client"))]
pub(crate) unsafe fn from_bytes_unchecked(value: Bytes) -> Self {
Self(value)
}
}
impl Deref for Utf8Bytes {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for Utf8Bytes {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for Utf8Bytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsRef<Bytes> for Utf8Bytes {
fn as_ref(&self) -> &Bytes {
&self.0
}
}
impl Borrow<str> for Utf8Bytes {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Utf8Bytes {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl TryFrom<Bytes> for Utf8Bytes {
type Error = std::str::Utf8Error;
fn try_from(value: Bytes) -> Result<Self, Self::Error> {
std::str::from_utf8(&value)?;
Ok(Self(value))
}
}
impl TryFrom<Vec<u8>> for Utf8Bytes {
type Error = std::str::Utf8Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Bytes::from(value).try_into()
}
}
impl From<String> for Utf8Bytes {
fn from(value: String) -> Self {
Self(Bytes::from(value))
}
}
impl From<&str> for Utf8Bytes {
fn from(value: &str) -> Self {
Self(Bytes::copy_from_slice(value.as_bytes()))
}
}
impl From<&String> for Utf8Bytes {
fn from(value: &String) -> Self {
value.as_str().into()
}
}
impl From<Utf8Bytes> for Bytes {
fn from(value: Utf8Bytes) -> Self {
value.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CloseFrame {
pub code: u16,
pub reason: Utf8Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum WireMessage {
Text(Utf8Bytes),
Binary(Bytes),
Ping(Bytes),
Pong(Bytes),
Close(Option<CloseFrame>),
}
pub type WsMessage = WireMessage;
impl From<String> for WireMessage {
fn from(s: String) -> Self {
Self::Text(s.into())
}
}
impl From<&str> for WireMessage {
fn from(s: &str) -> Self {
Self::Text(s.into())
}
}
impl From<Vec<u8>> for WireMessage {
fn from(value: Vec<u8>) -> Self {
Self::Binary(value.into())
}
}
impl WireMessage {
pub fn text(value: impl Into<Utf8Bytes>) -> Self {
Self::Text(value.into())
}
pub fn binary(value: impl Into<Bytes>) -> Self {
Self::Binary(value.into())
}
pub fn ping(value: impl Into<Bytes>) -> Self {
Self::Ping(value.into())
}
pub fn pong(value: impl Into<Bytes>) -> Self {
Self::Pong(value.into())
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(t) => Some(t.as_str()),
Self::Binary(b) => std::str::from_utf8(b).ok(),
_ => None,
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Text(text) => text.as_ref(),
Self::Binary(data) | Self::Ping(data) | Self::Pong(data) => data,
Self::Close(None) => &[],
Self::Close(Some(frame)) => frame.reason.as_ref(),
}
}
pub fn into_data(self) -> Bytes {
match self {
Self::Text(text) => text.into_bytes(),
Self::Binary(data) | Self::Ping(data) | Self::Pong(data) => data,
Self::Close(None) => Bytes::new(),
Self::Close(Some(frame)) => frame.reason.into_bytes(),
}
}
pub fn len(&self) -> usize {
self.as_bytes().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_close(&self) -> bool {
matches!(self, Self::Close(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn owned_inputs_transfer_their_allocations() {
let text = String::from("owned text payload");
let text_pointer = text.as_ptr();
let WireMessage::Text(text) = WireMessage::text(text) else {
unreachable!()
};
assert_eq!(text.as_bytes().as_ptr(), text_pointer);
let binary = vec![1, 2, 3, 4, 5];
let binary_pointer = binary.as_ptr();
let WireMessage::Binary(binary) = WireMessage::binary(binary) else {
unreachable!()
};
assert_eq!(binary.as_ptr(), binary_pointer);
}
#[test]
fn bytes_are_validated_before_becoming_text() {
assert!(Utf8Bytes::try_from(Bytes::from_static(b"valid text")).is_ok());
assert!(Utf8Bytes::try_from(Bytes::from_static(b"invalid \xff")).is_err());
}
}