use std::ops::Deref;
use bytes::Bytes;
#[derive(Clone, Default, PartialEq, Eq, Hash)]
pub struct Payload(Bytes);
impl Payload {
#[must_use]
pub fn new() -> Self {
Payload(Bytes::new())
}
#[must_use]
pub fn from_static(bytes: &'static [u8]) -> Self {
Payload(Bytes::from_static(bytes))
}
#[must_use]
pub fn copy_from_slice(data: &[u8]) -> Self {
Payload(Bytes::copy_from_slice(data))
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn into_bytes(self) -> Bytes {
self.0
}
}
impl Deref for Payload {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl AsRef<[u8]> for Payload {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl std::borrow::Borrow<[u8]> for Payload {
fn borrow(&self) -> &[u8] {
&self.0
}
}
impl std::fmt::Debug for Payload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Payload({} bytes)", self.0.len())
}
}
impl From<Bytes> for Payload {
fn from(b: Bytes) -> Self {
Payload(b)
}
}
impl From<Payload> for Bytes {
fn from(p: Payload) -> Self {
p.0
}
}
impl From<Vec<u8>> for Payload {
fn from(v: Vec<u8>) -> Self {
Payload(Bytes::from(v))
}
}
impl From<&[u8]> for Payload {
fn from(s: &[u8]) -> Self {
Payload(Bytes::copy_from_slice(s))
}
}
impl From<&str> for Payload {
fn from(s: &str) -> Self {
Payload(Bytes::copy_from_slice(s.as_bytes()))
}
}
impl From<String> for Payload {
fn from(s: String) -> Self {
Payload(Bytes::from(s.into_bytes()))
}
}
impl PartialEq<[u8]> for Payload {
fn eq(&self, other: &[u8]) -> bool {
self.0.as_ref() == other
}
}
impl PartialEq<Vec<u8>> for Payload {
fn eq(&self, other: &Vec<u8>) -> bool {
self.0.as_ref() == other.as_slice()
}
}
impl PartialEq<&[u8]> for Payload {
fn eq(&self, other: &&[u8]) -> bool {
self.0.as_ref() == *other
}
}
impl PartialEq<Payload> for [u8] {
fn eq(&self, other: &Payload) -> bool {
self == other.0.as_ref()
}
}
impl PartialEq<Payload> for Vec<u8> {
fn eq(&self, other: &Payload) -> bool {
self.as_slice() == other.0.as_ref()
}
}
impl PartialEq<Payload> for &[u8] {
fn eq(&self, other: &Payload) -> bool {
*self == other.0.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_static_is_byte_identical() {
let p = Payload::from_static(b"hello");
assert_eq!(&p[..], b"hello");
assert_eq!(p.len(), 5);
assert!(!p.is_empty());
}
#[test]
fn from_str_and_string_carry_utf8_bytes() {
assert_eq!(&Payload::from("héllo")[..], "héllo".as_bytes());
assert_eq!(&Payload::from(String::from("world"))[..], b"world");
}
#[test]
fn from_vec_and_slice_round_trip() {
assert_eq!(&Payload::from(vec![1u8, 2, 3])[..], &[1, 2, 3]);
assert_eq!(&Payload::from(&[4u8, 5][..])[..], &[4, 5]);
}
#[test]
fn new_and_default_are_empty() {
assert!(Payload::new().is_empty());
assert!(Payload::default().is_empty());
}
#[test]
fn clone_shares_buffer_and_compares_equal() {
let a = Payload::from(vec![9u8; 32]);
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn debug_prints_length_not_contents() {
let p = Payload::from_static(b"super-secret-token");
let dbg = format!("{p:?}");
assert!(!dbg.contains("secret"), "Debug leaked payload bytes: {dbg}");
assert_eq!(dbg, "Payload(18 bytes)");
}
#[test]
fn bytes_round_trip_is_zero_copy_identity() {
let b = Bytes::from_static(b"abc");
let p = Payload::from(b.clone());
assert_eq!(Bytes::from(p), b);
}
#[test]
fn partial_eq_against_slice_vec_and_ref_slice() {
let p = Payload::from_static(b"abc");
assert_eq!(p, *b"abc".as_slice());
assert_ne!(p, *b"abd".as_slice());
assert_eq!(p, b"abc".to_vec());
assert_ne!(p, b"ab".to_vec());
let s: &[u8] = b"abc";
assert_eq!(p, s);
let s2: &[u8] = b"abcd";
assert_ne!(p, s2);
}
#[test]
fn partial_eq_is_symmetric_across_types() {
let p = Payload::from_static(b"abc");
assert_eq!(*b"abc".as_slice(), p);
assert_ne!(*b"abd".as_slice(), p);
assert_eq!(b"abc".to_vec(), p);
assert_ne!(b"ab".to_vec(), p);
let s: &[u8] = b"abc";
assert_eq!(s, p);
let s2: &[u8] = b"abcd";
assert_ne!(s2, p);
}
#[test]
fn borrow_and_hash_agree_with_slice() {
use std::borrow::Borrow;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn hash_of<T: Hash + ?Sized>(t: &T) -> u64 {
let mut h = DefaultHasher::new();
t.hash(&mut h);
h.finish()
}
let p = Payload::from_static(b"key-bytes");
let slice: &[u8] = b"key-bytes";
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, slice);
assert_eq!(
hash_of(&p),
hash_of(slice),
"Hash(Payload) must match Hash(&[u8]) for Borrow<[u8]> lookups"
);
}
#[test]
fn hashset_keyed_by_payload_is_queryable_by_slice() {
use std::collections::HashSet;
let mut set: HashSet<Payload> = HashSet::new();
set.insert(Payload::from_static(b"present"));
assert!(set.contains(b"present".as_slice()));
assert!(!set.contains(b"absent".as_slice()));
}
}