Skip to main content

http_wasm_guest/host/
bytes.rs

1use std::{
2    borrow::Borrow,
3    fmt::Display,
4    ops::Deref,
5    str::{Utf8Error, from_utf8},
6};
7
8/// Owned container for binary data used throughout the API.
9///
10/// `Bytes` stores its contents as a boxed slice for efficient cloning and
11/// sharing. It is suitable for HTTP headers, bodies, and configuration payloads,
12/// and can be created from common byte-oriented types.
13///
14/// Use [`to_str`](Bytes::to_str) to interpret the contents as UTF-8.
15#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Hash, Default)]
16pub struct Bytes(Box<[u8]>);
17
18// --- Core API Methods ---
19
20impl Bytes {
21    /// Returns the contents as UTF-8 if valid.
22    ///
23    /// This is a zero-copy view into the underlying bytes. If the data is not
24    /// valid UTF-8, an error is returned.
25    pub fn to_str(&self) -> Result<&str, Utf8Error> {
26        from_utf8(self.0.as_ref())
27    }
28}
29
30// --- Standard Library Trait Implementations (for Bytes) ---
31
32impl Deref for Bytes {
33    type Target = [u8];
34
35    fn deref(&self) -> &Self::Target {
36        self.0.as_ref()
37    }
38}
39
40impl Display for Bytes {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        write!(f, "{}", String::from_utf8_lossy(self.0.as_ref()))
43    }
44}
45
46impl Borrow<[u8]> for Bytes {
47    fn borrow(&self) -> &[u8] {
48        self.0.as_ref()
49    }
50}
51
52// --- Comparison Trait Implementations ---
53impl PartialEq<Bytes> for [u8] {
54    fn eq(&self, other: &Bytes) -> bool {
55        self == other.0.as_ref()
56    }
57}
58
59impl PartialEq<[u8]> for Bytes {
60    fn eq(&self, other: &[u8]) -> bool {
61        self.0.as_ref() == other
62    }
63}
64
65impl PartialEq<Bytes> for &[u8] {
66    fn eq(&self, other: &Bytes) -> bool {
67        *self == other.0.as_ref()
68    }
69}
70
71impl PartialEq<&[u8]> for Bytes {
72    fn eq(&self, other: &&[u8]) -> bool {
73        self.0.as_ref() == *other
74    }
75}
76
77impl<const N: usize> PartialEq<[u8; N]> for Bytes {
78    fn eq(&self, other: &[u8; N]) -> bool {
79        self.0.as_ref() == other
80    }
81}
82
83impl<const N: usize> PartialEq<Bytes> for [u8; N] {
84    fn eq(&self, other: &Bytes) -> bool {
85        self == other.0.as_ref()
86    }
87}
88
89impl<const N: usize> PartialEq<&[u8; N]> for Bytes {
90    fn eq(&self, other: &&[u8; N]) -> bool {
91        self.0.as_ref() == *other
92    }
93}
94impl<const N: usize> PartialEq<Bytes> for &[u8; N] {
95    fn eq(&self, other: &Bytes) -> bool {
96        *self == other.0.as_ref()
97    }
98}
99
100impl PartialEq<str> for Bytes {
101    fn eq(&self, other: &str) -> bool {
102        match self.to_str() {
103            Ok(s) => s == other,
104            Err(_) => false,
105        }
106    }
107}
108
109impl PartialEq<&str> for Bytes {
110    fn eq(&self, other: &&str) -> bool {
111        match self.to_str() {
112            Ok(s) => s == *other,
113            Err(_) => false,
114        }
115    }
116}
117
118impl PartialEq<Bytes> for str {
119    fn eq(&self, other: &Bytes) -> bool {
120        self.as_bytes() == other.0.as_ref()
121    }
122}
123impl PartialEq<Bytes> for &str {
124    fn eq(&self, other: &Bytes) -> bool {
125        self.as_bytes() == other.0.as_ref()
126    }
127}
128
129// --- Conversion Trait Implementations (From<...> for Bytes) ---
130
131/// Creates a `Bytes` value from an existing boxed slice without copying.
132impl From<Box<[u8]>> for Bytes {
133    fn from(value: Box<[u8]>) -> Self {
134        Self(value)
135    }
136}
137
138/// Creates a `Bytes` value by copying a byte slice.
139impl From<&[u8]> for Bytes {
140    fn from(value: &[u8]) -> Self {
141        Self(value.to_vec().into_boxed_slice())
142    }
143}
144
145/// Creates a `Bytes` value by copying a byte slice.
146impl<const N: usize> From<&[u8; N]> for Bytes {
147    fn from(value: &[u8; N]) -> Self {
148        Self(Box::new(*value))
149    }
150}
151
152/// Creates a `Bytes` value by taking ownership of a byte vector.
153///
154/// This avoids an extra copy by converting the `Vec<u8>` into a boxed slice.
155impl From<Vec<u8>> for Bytes {
156    fn from(value: Vec<u8>) -> Self {
157        Self(value.into_boxed_slice())
158    }
159}
160
161/// Creates a `Bytes` value from a UTF-8 string slice.
162///
163/// The string is copied into an owned byte buffer.
164impl From<&str> for Bytes {
165    fn from(value: &str) -> Self {
166        Self(value.as_bytes().into())
167    }
168}
169
170// --- Test Module ---
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use std::collections::HashSet;
176
177    #[test]
178    fn test_display_valid_utf8() {
179        let s = "valid";
180        let b = Bytes::from(s);
181        assert_eq!(format!("{b}"), s);
182    }
183
184    #[test]
185    fn test_display_invalid_utf8() {
186        let b = Bytes::from(vec![0x48, 0xFF, 0x6c, 0x6c, 0x6f]);
187        assert_eq!(format!("{b}"), "H�llo");
188    }
189
190    #[test]
191    fn bytes_from_string_slice_roundtrip() {
192        let original = "Hello, http-wasm!";
193        let bytes = Bytes::from(original);
194        assert_eq!(bytes.to_str().unwrap(), original);
195    }
196
197    #[test]
198    fn bytes_from_byte_slice_roundtrip() {
199        let original: &[u8] = b"binary data \x00\x01\x02";
200        let bytes = Bytes::from(original);
201        assert_eq!(&bytes, original);
202    }
203
204    #[test]
205    fn bytes_from_vec_roundtrip() {
206        let original = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello"
207        let bytes = Bytes::from(original.clone());
208        assert_eq!(&bytes, original.as_slice());
209    }
210
211    #[test]
212    fn bytes_from_boxed_slice() {
213        let boxed: Box<[u8]> = vec![1, 2, 3, 4, 5].into_boxed_slice();
214        let bytes = Bytes::from(boxed.clone());
215        assert_eq!(&bytes, boxed.as_ref());
216    }
217
218    #[test]
219    fn bytes_equality() {
220        let a = Bytes::from("test");
221        let b = Bytes::from("test");
222        let c = Bytes::from("different");
223
224        assert_eq!(a, b);
225        assert_ne!(a, c);
226    }
227
228    #[test]
229    fn bytes_clone() {
230        let original = Bytes::from("clone me");
231        let cloned = original.clone();
232        assert_eq!(original, cloned);
233    }
234
235    #[test]
236    fn bytes_display() {
237        let bytes = Bytes::from("display test");
238        assert_eq!(format!("{}", bytes), "display test");
239    }
240
241    #[test]
242    fn bytes_empty() {
243        let empty = Bytes::from("");
244        assert!(empty.is_empty());
245        assert_eq!(empty.len(), 0);
246    }
247
248    #[test]
249    fn bytes_deref_to_slice() {
250        let bytes = Bytes::from("deref");
251        let slice: &[u8] = &bytes;
252        assert_eq!(slice, b"deref");
253    }
254
255    #[test]
256    fn bytes_hash_consistency() {
257        let a = Bytes::from("hash me");
258        let b = Bytes::from("hash me");
259
260        let mut set = HashSet::new();
261        set.insert(a.clone());
262
263        assert!(set.contains(&b));
264        assert_eq!(set.len(), 1);
265    }
266
267    #[test]
268    fn bytes_invalid_utf8_to_str() {
269        let invalid = Bytes::from(vec![0xFF, 0xFE]);
270        assert!(invalid.to_str().is_err());
271    }
272
273    #[test]
274    fn bytes_display_invalid_utf8() {
275        // When displaying invalid UTF-8, it should show the error message
276        let invalid = Bytes::from(vec![0xFF, 0xFE]);
277        let displayed = format!("{}", invalid);
278        // The display should contain error info since it's invalid UTF-8
279        assert!(!displayed.is_empty());
280    }
281
282    #[test]
283    fn slice_eq_bytes() {
284        let slice: &[u8] = &vec![1, 2, 3, 4][..];
285        let bytes = Bytes::from(vec![1, 2, 3, 4]);
286        assert_eq!(slice, bytes);
287        assert_eq!(&*slice, &bytes);
288
289        assert!(bytes.eq(slice));
290        assert!(bytes.eq(&slice));
291        assert!(&bytes.eq(slice));
292        assert!(slice.eq(&bytes));
293    }
294
295    #[test]
296    fn array_slice_eq_bytes() {
297        let arr: &[u8; 4] = &[1, 2, 3, 4];
298        let bytes = Bytes::from(vec![1, 2, 3, 4]);
299        assert!(bytes.eq(&arr));
300        assert!(&bytes.eq(&arr));
301        assert!(arr.eq(&bytes));
302        assert_eq!(arr, bytes);
303    }
304
305    #[test]
306    fn str_slice_eq_bytes() {
307        let str = "test";
308        let bytes = Bytes::from("test");
309        assert_eq!(str, bytes);
310        assert!(bytes.eq(str));
311        assert!(&bytes.eq(str));
312        assert!(str.eq(&bytes));
313    }
314
315    #[test]
316    fn bytes_partial_str_invalid_bytes() {
317        let a = "test";
318        let b = Bytes::from(vec![0xFF, 0xFE]);
319
320        assert!(!b.eq(a));
321        assert!(!b.eq(&a));
322        assert!(!a.eq(&b));
323    }
324
325    #[test]
326    fn bytes_borrow() {
327        let a = b"test";
328        let b = Bytes::from(a);
329        let set: HashSet<Bytes> = HashSet::from([Bytes::from(a)]);
330        assert!(set.contains(&b));
331
332        assert_eq!(set.get(&b[..]), Some(&b));
333        assert_eq!(set.get(&b), Some(&b));
334    }
335}