use std::{
borrow::Borrow,
fmt::Display,
ops::Deref,
str::{Utf8Error, from_utf8},
};
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Hash, Default)]
pub struct Bytes(Box<[u8]>);
impl Bytes {
pub fn to_str(&self) -> Result<&str, Utf8Error> {
from_utf8(&self.0)
}
}
impl Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl Display for Bytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", String::from_utf8_lossy(&self.0))
}
}
impl Borrow<[u8]> for Bytes {
fn borrow(&self) -> &[u8] {
self.as_ref()
}
}
impl<const N: usize> Borrow<[u8; N]> for Bytes {
fn borrow(&self) -> &[u8; N] {
debug_assert!(self.len() == N);
unsafe { &*(self.as_ref() as *const [u8] as *const [u8; N]) }
}
}
impl<const N: usize> PartialEq<[u8; N]> for Bytes {
fn eq(&self, other: &[u8; N]) -> bool {
self.as_ref() == other
}
}
impl<const N: usize> PartialEq<Bytes> for [u8; N] {
fn eq(&self, other: &Bytes) -> bool {
self == other.as_ref()
}
}
impl<const N: usize> PartialEq<&[u8; N]> for Bytes {
fn eq(&self, other: &&[u8; N]) -> bool {
self == *other
}
}
impl PartialEq<str> for Bytes {
fn eq(&self, other: &str) -> bool {
match self.to_str() {
Ok(s) => s == other,
Err(_) => false,
}
}
}
impl PartialEq<&str> for Bytes {
fn eq(&self, other: &&str) -> bool {
match self.to_str() {
Ok(s) => s == *other,
Err(_) => false,
}
}
}
impl PartialEq<Bytes> for str {
fn eq(&self, other: &Bytes) -> bool {
self.as_bytes() == other.as_ref()
}
}
impl From<Box<[u8]>> for Bytes {
fn from(value: Box<[u8]>) -> Self {
Self(value)
}
}
impl From<&[u8]> for Bytes {
fn from(value: &[u8]) -> Self {
Self(value.to_vec().into_boxed_slice())
}
}
impl<const N: usize> From<&[u8; N]> for Bytes {
fn from(value: &[u8; N]) -> Self {
Self(Box::new(*value))
}
}
impl From<Vec<u8>> for Bytes {
fn from(value: Vec<u8>) -> Self {
Self(value.into_boxed_slice())
}
}
impl From<&str> for Bytes {
fn from(value: &str) -> Self {
Self(value.as_bytes().into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_display_valid_utf8() {
let s = "valid";
let b = Bytes::from(s);
assert_eq!(format!("{b}"), s);
}
#[test]
fn test_display_invalid_utf8() {
let b = Bytes::from(vec![0x48, 0xFF, 0x6c, 0x6c, 0x6f]);
assert_eq!(format!("{b}"), "H�llo");
}
#[test]
fn bytes_from_string_slice_roundtrip() {
let original = "Hello, http-wasm!";
let bytes = Bytes::from(original);
assert_eq!(bytes.to_str().unwrap(), original);
}
#[test]
fn bytes_from_byte_slice_roundtrip() {
let original: &[u8] = b"binary data \x00\x01\x02";
let bytes = Bytes::from(original);
assert_eq!(bytes.as_ref(), original);
}
#[test]
fn bytes_from_vec_roundtrip() {
let original = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; let bytes = Bytes::from(original.clone());
assert_eq!(bytes.as_ref(), original.as_slice());
}
#[test]
fn bytes_from_boxed_slice() {
let boxed: Box<[u8]> = vec![1, 2, 3, 4, 5].into_boxed_slice();
let bytes = Bytes::from(boxed.clone());
assert_eq!(bytes.as_ref(), boxed.as_ref());
}
#[test]
fn bytes_equality() {
let a = Bytes::from("test");
let b = Bytes::from("test");
let c = Bytes::from("different");
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn bytes_clone() {
let original = Bytes::from("clone me");
let cloned = original.clone();
assert_eq!(original, cloned);
}
#[test]
fn bytes_display() {
let bytes = Bytes::from("display test");
assert_eq!(format!("{}", bytes), "display test");
}
#[test]
fn bytes_empty() {
let empty = Bytes::from("");
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
}
#[test]
fn bytes_deref_to_slice() {
let bytes = Bytes::from("deref");
let slice: &[u8] = &bytes;
assert_eq!(slice, b"deref");
}
#[test]
fn bytes_hash_consistency() {
let a = Bytes::from("hash me");
let b = Bytes::from("hash me");
let mut set = HashSet::new();
set.insert(a.clone());
assert!(set.contains(&b));
assert_eq!(set.len(), 1);
}
#[test]
fn bytes_invalid_utf8_to_str() {
let invalid = Bytes::from(vec![0xFF, 0xFE]);
assert!(invalid.to_str().is_err());
}
#[test]
fn bytes_display_invalid_utf8() {
let invalid = Bytes::from(vec![0xFF, 0xFE]);
let displayed = format!("{}", invalid);
assert!(!displayed.is_empty());
}
#[test]
fn bytes_partial_eq_u8() {
let a = b"test";
let b = Bytes::from(a);
assert!(b.eq(a));
assert!(a.eq(&b));
}
#[test]
fn bytes_partial_eq_str() {
let a = "test";
let b = Bytes::from(a);
assert!(b.eq(a));
assert!(&b.eq(a));
assert!(a.eq(&b));
}
#[test]
fn bytes_partial_str_invalid_bytes() {
let a = "test";
let b = Bytes::from(vec![0xFF, 0xFE]);
assert!(!b.eq(a));
assert!(!b.eq(&a));
assert!(!a.eq(&b));
}
#[test]
fn bytes_borrow() {
let a = b"test";
let b = Bytes::from(a);
let set: HashSet<Bytes> = HashSet::from([Bytes::from(a)]);
assert!(set.contains(a));
assert!(set.contains(&b));
assert_eq!(set.get(&b[..]), Some(&b));
assert_eq!(set.get(&b), Some(&b));
assert_eq!(set.get(a), Some(&b));
}
#[test]
fn bytes_borrow_unknown_key() {
let a = b"xxx";
let set: HashSet<Bytes> = HashSet::from([Bytes::from(b"test")]);
assert!(!set.contains(a));
}
}