1use std::ops::Deref;
4
5use bytes::Bytes;
6
7#[derive(Clone, Default, PartialEq, Eq, Hash)]
20pub struct Payload(Bytes);
21
22impl Payload {
23 #[must_use]
25 pub fn new() -> Self {
26 Payload(Bytes::new())
27 }
28
29 #[must_use]
31 pub fn from_static(bytes: &'static [u8]) -> Self {
32 Payload(Bytes::from_static(bytes))
33 }
34
35 #[must_use]
37 pub fn copy_from_slice(data: &[u8]) -> Self {
38 Payload(Bytes::copy_from_slice(data))
39 }
40
41 #[must_use]
43 pub fn as_slice(&self) -> &[u8] {
44 &self.0
45 }
46
47 #[must_use]
50 pub fn into_bytes(self) -> Bytes {
51 self.0
52 }
53}
54
55impl Deref for Payload {
56 type Target = [u8];
57
58 fn deref(&self) -> &[u8] {
59 &self.0
60 }
61}
62
63impl AsRef<[u8]> for Payload {
64 fn as_ref(&self) -> &[u8] {
65 &self.0
66 }
67}
68
69impl std::borrow::Borrow<[u8]> for Payload {
70 fn borrow(&self) -> &[u8] {
71 &self.0
72 }
73}
74
75impl std::fmt::Debug for Payload {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(f, "Payload({} bytes)", self.0.len())
80 }
81}
82
83impl From<Bytes> for Payload {
84 fn from(b: Bytes) -> Self {
85 Payload(b)
86 }
87}
88
89impl From<Payload> for Bytes {
90 fn from(p: Payload) -> Self {
91 p.0
92 }
93}
94
95impl From<Vec<u8>> for Payload {
96 fn from(v: Vec<u8>) -> Self {
97 Payload(Bytes::from(v))
98 }
99}
100
101impl From<&[u8]> for Payload {
102 fn from(s: &[u8]) -> Self {
103 Payload(Bytes::copy_from_slice(s))
104 }
105}
106
107impl From<&str> for Payload {
108 fn from(s: &str) -> Self {
111 Payload(Bytes::copy_from_slice(s.as_bytes()))
112 }
113}
114
115impl From<String> for Payload {
116 fn from(s: String) -> Self {
119 Payload(Bytes::from(s.into_bytes()))
120 }
121}
122
123impl PartialEq<[u8]> for Payload {
124 fn eq(&self, other: &[u8]) -> bool {
125 self.0.as_ref() == other
126 }
127}
128
129impl PartialEq<Vec<u8>> for Payload {
130 fn eq(&self, other: &Vec<u8>) -> bool {
131 self.0.as_ref() == other.as_slice()
132 }
133}
134
135impl PartialEq<&[u8]> for Payload {
136 fn eq(&self, other: &&[u8]) -> bool {
137 self.0.as_ref() == *other
138 }
139}
140
141impl PartialEq<Payload> for [u8] {
145 fn eq(&self, other: &Payload) -> bool {
146 self == other.0.as_ref()
147 }
148}
149
150impl PartialEq<Payload> for Vec<u8> {
151 fn eq(&self, other: &Payload) -> bool {
152 self.as_slice() == other.0.as_ref()
153 }
154}
155
156impl PartialEq<Payload> for &[u8] {
157 fn eq(&self, other: &Payload) -> bool {
158 *self == other.0.as_ref()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn from_static_is_byte_identical() {
168 let p = Payload::from_static(b"hello");
169 assert_eq!(&p[..], b"hello");
170 assert_eq!(p.len(), 5);
171 assert!(!p.is_empty());
172 }
173
174 #[test]
175 fn from_str_and_string_carry_utf8_bytes() {
176 assert_eq!(&Payload::from("héllo")[..], "héllo".as_bytes());
177 assert_eq!(&Payload::from(String::from("world"))[..], b"world");
178 }
179
180 #[test]
181 fn from_vec_and_slice_round_trip() {
182 assert_eq!(&Payload::from(vec![1u8, 2, 3])[..], &[1, 2, 3]);
183 assert_eq!(&Payload::from(&[4u8, 5][..])[..], &[4, 5]);
184 }
185
186 #[test]
187 fn new_and_default_are_empty() {
188 assert!(Payload::new().is_empty());
189 assert!(Payload::default().is_empty());
190 }
191
192 #[test]
193 fn clone_shares_buffer_and_compares_equal() {
194 let a = Payload::from(vec![9u8; 32]);
195 let b = a.clone();
196 assert_eq!(a, b);
197 }
198
199 #[test]
200 fn debug_prints_length_not_contents() {
201 let p = Payload::from_static(b"super-secret-token");
202 let dbg = format!("{p:?}");
203 assert!(!dbg.contains("secret"), "Debug leaked payload bytes: {dbg}");
204 assert_eq!(dbg, "Payload(18 bytes)");
205 }
206
207 #[test]
208 fn bytes_round_trip_is_zero_copy_identity() {
209 let b = Bytes::from_static(b"abc");
210 let p = Payload::from(b.clone());
211 assert_eq!(Bytes::from(p), b);
212 }
213
214 #[test]
217 fn partial_eq_against_slice_vec_and_ref_slice() {
218 let p = Payload::from_static(b"abc");
219 assert_eq!(p, *b"abc".as_slice());
221 assert_ne!(p, *b"abd".as_slice());
222 assert_eq!(p, b"abc".to_vec());
224 assert_ne!(p, b"ab".to_vec());
225 let s: &[u8] = b"abc";
227 assert_eq!(p, s);
228 let s2: &[u8] = b"abcd";
229 assert_ne!(p, s2);
230 }
231
232 #[test]
235 fn partial_eq_is_symmetric_across_types() {
236 let p = Payload::from_static(b"abc");
237 assert_eq!(*b"abc".as_slice(), p);
239 assert_ne!(*b"abd".as_slice(), p);
240 assert_eq!(b"abc".to_vec(), p);
242 assert_ne!(b"ab".to_vec(), p);
243 let s: &[u8] = b"abc";
245 assert_eq!(s, p);
246 let s2: &[u8] = b"abcd";
247 assert_ne!(s2, p);
248 }
249
250 #[test]
255 fn borrow_and_hash_agree_with_slice() {
256 use std::borrow::Borrow;
257 use std::collections::hash_map::DefaultHasher;
258 use std::hash::{Hash, Hasher};
259
260 fn hash_of<T: Hash + ?Sized>(t: &T) -> u64 {
261 let mut h = DefaultHasher::new();
262 t.hash(&mut h);
263 h.finish()
264 }
265
266 let p = Payload::from_static(b"key-bytes");
267 let slice: &[u8] = b"key-bytes";
268
269 let borrowed: &[u8] = p.borrow();
271 assert_eq!(borrowed, slice);
272 assert_eq!(
274 hash_of(&p),
275 hash_of(slice),
276 "Hash(Payload) must match Hash(&[u8]) for Borrow<[u8]> lookups"
277 );
278 }
279
280 #[test]
283 fn hashset_keyed_by_payload_is_queryable_by_slice() {
284 use std::collections::HashSet;
285 let mut set: HashSet<Payload> = HashSet::new();
286 set.insert(Payload::from_static(b"present"));
287 assert!(set.contains(b"present".as_slice()));
288 assert!(!set.contains(b"absent".as_slice()));
289 }
290}