bytestring/
lib.rs

1//! A UTF-8 encoded read-only string using `Bytes` as storage.
2//!
3//! See docs for [`ByteString`].
4
5#![no_std]
6#![deny(rust_2018_idioms, nonstandard_style)]
7#![warn(future_incompatible, missing_docs)]
8
9extern crate alloc;
10
11use alloc::{
12    boxed::Box,
13    string::{String, ToString},
14    vec::Vec,
15};
16use core::{borrow::Borrow, convert::TryFrom, fmt, hash, ops, str};
17
18use bytes::Bytes;
19
20/// An immutable UTF-8 encoded string with [`Bytes`] as a storage.
21#[derive(Clone, Default, Eq, PartialOrd, Ord)]
22pub struct ByteString(Bytes);
23
24impl ByteString {
25    /// Creates a new empty `ByteString`.
26    pub const fn new() -> Self {
27        ByteString(Bytes::new())
28    }
29
30    /// Get a reference to the underlying `Bytes` object.
31    pub fn as_bytes(&self) -> &Bytes {
32        &self.0
33    }
34
35    /// Unwraps this `ByteString` into the underlying `Bytes` object.
36    pub fn into_bytes(self) -> Bytes {
37        self.0
38    }
39
40    /// Creates a new `ByteString` from a `&'static str`.
41    pub const fn from_static(src: &'static str) -> ByteString {
42        Self(Bytes::from_static(src.as_bytes()))
43    }
44
45    /// Creates a new `ByteString` from a Bytes.
46    ///
47    /// # Safety
48    /// This function is unsafe because it does not check the bytes passed to it are valid UTF-8.
49    /// If this constraint is violated, it may cause memory unsafety issues with future users of
50    /// the `ByteString`, as we assume that `ByteString`s are valid UTF-8. However, the most likely
51    /// issue is that the data gets corrupted.
52    pub const unsafe fn from_bytes_unchecked(src: Bytes) -> ByteString {
53        Self(src)
54    }
55
56    /// Returns a new byte string that is equivalent to the given `subset`.
57    ///
58    /// When processing a `ByteString` buffer with other tools, one often gets a `&str` which is in
59    /// fact a slice of the original `ByteString`; i.e., a subset of it. This function turns that
60    /// `&str` into another `ByteString`, as if one had sliced the `ByteString` with the offsets
61    /// that correspond to `subset`.
62    ///
63    /// Corresponds to [`Bytes::slice_ref`].
64    ///
65    /// This operation is `O(1)`.
66    ///
67    /// # Panics
68    ///
69    /// Panics if `subset` is not a sub-slice of this byte string.
70    ///
71    /// Note that strings which are only subsets from an equality perspective do not uphold this
72    /// requirement; see examples.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// # use bytestring::ByteString;
78    /// let string = ByteString::from_static(" foo ");
79    /// let subset = string.trim();
80    /// let substring = string.slice_ref(subset);
81    /// assert_eq!(substring, "foo");
82    /// ```
83    ///
84    /// ```should_panic
85    /// # use bytestring::ByteString;
86    /// // panics because the given slice is not derived from the original byte string, despite
87    /// // being a logical subset of the string
88    /// ByteString::from_static("foo bar").slice_ref("foo");
89    /// ```
90    pub fn slice_ref(&self, subset: &str) -> Self {
91        Self(self.0.slice_ref(subset.as_bytes()))
92    }
93}
94
95impl PartialEq<str> for ByteString {
96    fn eq(&self, other: &str) -> bool {
97        &self[..] == other
98    }
99}
100
101impl<T: AsRef<str>> PartialEq<T> for ByteString {
102    fn eq(&self, other: &T) -> bool {
103        &self[..] == other.as_ref()
104    }
105}
106
107impl AsRef<ByteString> for ByteString {
108    fn as_ref(&self) -> &ByteString {
109        self
110    }
111}
112
113impl AsRef<[u8]> for ByteString {
114    fn as_ref(&self) -> &[u8] {
115        self.0.as_ref()
116    }
117}
118
119impl AsRef<str> for ByteString {
120    fn as_ref(&self) -> &str {
121        self
122    }
123}
124
125impl hash::Hash for ByteString {
126    fn hash<H: hash::Hasher>(&self, state: &mut H) {
127        (**self).hash(state);
128    }
129}
130
131impl ops::Deref for ByteString {
132    type Target = str;
133
134    #[inline]
135    fn deref(&self) -> &str {
136        let bytes = self.0.as_ref();
137        // SAFETY: UTF-8 validity is guaranteed during construction.
138        unsafe { str::from_utf8_unchecked(bytes) }
139    }
140}
141
142impl Borrow<str> for ByteString {
143    fn borrow(&self) -> &str {
144        self
145    }
146}
147
148impl From<String> for ByteString {
149    #[inline]
150    fn from(value: String) -> Self {
151        Self(Bytes::from(value))
152    }
153}
154
155impl From<&str> for ByteString {
156    #[inline]
157    fn from(value: &str) -> Self {
158        Self(Bytes::copy_from_slice(value.as_ref()))
159    }
160}
161
162impl From<Box<str>> for ByteString {
163    #[inline]
164    fn from(value: Box<str>) -> Self {
165        Self(Bytes::from(value.into_boxed_bytes()))
166    }
167}
168
169impl From<ByteString> for String {
170    #[inline]
171    fn from(value: ByteString) -> Self {
172        value.to_string()
173    }
174}
175
176impl TryFrom<&[u8]> for ByteString {
177    type Error = str::Utf8Error;
178
179    #[inline]
180    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
181        let _ = str::from_utf8(value)?;
182        Ok(ByteString(Bytes::copy_from_slice(value)))
183    }
184}
185
186impl TryFrom<Vec<u8>> for ByteString {
187    type Error = str::Utf8Error;
188
189    #[inline]
190    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
191        let buf = String::from_utf8(value).map_err(|err| err.utf8_error())?;
192        Ok(ByteString(Bytes::from(buf)))
193    }
194}
195
196impl TryFrom<Bytes> for ByteString {
197    type Error = str::Utf8Error;
198
199    #[inline]
200    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
201        let _ = str::from_utf8(value.as_ref())?;
202        Ok(ByteString(value))
203    }
204}
205
206impl TryFrom<bytes::BytesMut> for ByteString {
207    type Error = str::Utf8Error;
208
209    #[inline]
210    fn try_from(value: bytes::BytesMut) -> Result<Self, Self::Error> {
211        let _ = str::from_utf8(&value)?;
212        Ok(ByteString(value.freeze()))
213    }
214}
215
216macro_rules! array_impls {
217    ($($len:expr)+) => {
218        $(
219            impl TryFrom<[u8; $len]> for ByteString {
220                type Error = str::Utf8Error;
221
222                #[inline]
223                fn try_from(value: [u8; $len]) -> Result<Self, Self::Error> {
224                    ByteString::try_from(&value[..])
225                }
226            }
227
228            impl TryFrom<&[u8; $len]> for ByteString {
229                type Error = str::Utf8Error;
230
231                #[inline]
232                fn try_from(value: &[u8; $len]) -> Result<Self, Self::Error> {
233                    ByteString::try_from(&value[..])
234                }
235            }
236        )+
237    }
238}
239
240array_impls!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32);
241
242impl fmt::Debug for ByteString {
243    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
244        (**self).fmt(fmt)
245    }
246}
247
248impl fmt::Display for ByteString {
249    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
250        (**self).fmt(fmt)
251    }
252}
253
254#[cfg(feature = "serde")]
255mod serde {
256    use alloc::string::String;
257
258    use serde::{
259        de::{Deserialize, Deserializer},
260        ser::{Serialize, Serializer},
261    };
262
263    use super::ByteString;
264
265    impl Serialize for ByteString {
266        #[inline]
267        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
268        where
269            S: Serializer,
270        {
271            serializer.serialize_str(self.as_ref())
272        }
273    }
274
275    impl<'de> Deserialize<'de> for ByteString {
276        #[inline]
277        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
278        where
279            D: Deserializer<'de>,
280        {
281            String::deserialize(deserializer).map(ByteString::from)
282        }
283    }
284
285    #[cfg(test)]
286    mod serde_impl_tests {
287        use serde::de::DeserializeOwned;
288        use static_assertions::assert_impl_all;
289
290        use super::*;
291
292        assert_impl_all!(ByteString: Serialize, DeserializeOwned);
293    }
294}
295
296#[cfg(test)]
297mod test {
298    use alloc::{borrow::ToOwned, format, vec};
299    use core::{
300        hash::{Hash, Hasher},
301        panic::{RefUnwindSafe, UnwindSafe},
302    };
303
304    use ahash::AHasher;
305    use static_assertions::assert_impl_all;
306
307    use super::*;
308
309    assert_impl_all!(ByteString: Send, Sync, Unpin, Sized);
310    assert_impl_all!(ByteString: Clone, Default, Eq, PartialOrd, Ord);
311    assert_impl_all!(ByteString: fmt::Debug, fmt::Display);
312    assert_impl_all!(ByteString: UnwindSafe, RefUnwindSafe);
313
314    #[test]
315    fn eq() {
316        let s: ByteString = ByteString::from_static("test");
317        assert_eq!(s, "test");
318        assert_eq!(s, *"test");
319        assert_eq!(s, "test".to_owned());
320    }
321
322    #[test]
323    fn new() {
324        let _: ByteString = ByteString::new();
325    }
326
327    #[test]
328    fn as_bytes() {
329        let buf = ByteString::new();
330        assert!(buf.as_bytes().is_empty());
331
332        let buf = ByteString::from("hello");
333        assert_eq!(buf.as_bytes(), "hello");
334    }
335
336    #[test]
337    fn from_bytes_unchecked() {
338        let buf = unsafe { ByteString::from_bytes_unchecked(Bytes::new()) };
339        assert!(buf.is_empty());
340
341        let buf = unsafe { ByteString::from_bytes_unchecked(Bytes::from("hello")) };
342        assert_eq!(buf, "hello");
343    }
344
345    #[test]
346    fn as_ref() {
347        let buf = ByteString::new();
348
349        let _: &ByteString = buf.as_ref();
350        let _: &[u8] = buf.as_ref();
351    }
352
353    #[test]
354    fn borrow() {
355        let buf = ByteString::new();
356
357        let _: &str = buf.borrow();
358    }
359
360    #[test]
361    fn hash() {
362        let mut hasher1 = AHasher::default();
363        "str".hash(&mut hasher1);
364
365        let mut hasher2 = AHasher::default();
366        let s = ByteString::from_static("str");
367        s.hash(&mut hasher2);
368        assert_eq!(hasher1.finish(), hasher2.finish());
369    }
370
371    #[test]
372    fn from_string() {
373        let s: ByteString = "hello".to_owned().into();
374        assert_eq!(&s, "hello");
375        let t: &str = s.as_ref();
376        assert_eq!(t, "hello");
377    }
378
379    #[test]
380    fn from_str() {
381        let _: ByteString = "str".into();
382        let _: ByteString = "str".to_owned().into_boxed_str().into();
383    }
384
385    #[test]
386    fn to_string() {
387        let buf = ByteString::from("foo");
388        assert_eq!(String::from(buf), "foo");
389    }
390
391    #[test]
392    fn from_static_str() {
393        static _S: ByteString = ByteString::from_static("hello");
394        let _ = ByteString::from_static("str");
395    }
396
397    #[test]
398    fn try_from_slice() {
399        let _ = ByteString::try_from(b"nice bytes").unwrap();
400    }
401
402    #[test]
403    fn try_from_array() {
404        assert_eq!(
405            ByteString::try_from([b'h', b'i']).unwrap(),
406            ByteString::from_static("hi")
407        );
408    }
409
410    #[test]
411    fn try_from_vec() {
412        let _ = ByteString::try_from(vec![b'f', b'o', b'o']).unwrap();
413        ByteString::try_from(vec![0, 159, 146, 150]).unwrap_err();
414    }
415
416    #[test]
417    fn try_from_bytes() {
418        let _ = ByteString::try_from(Bytes::from_static(b"nice bytes")).unwrap();
419    }
420
421    #[test]
422    fn try_from_bytes_mut() {
423        let _ = ByteString::try_from(bytes::BytesMut::from(&b"nice bytes"[..])).unwrap();
424    }
425
426    #[test]
427    fn display() {
428        let buf = ByteString::from("bar");
429        assert_eq!(format!("{buf}"), "bar");
430    }
431
432    #[test]
433    fn debug() {
434        let buf = ByteString::from("baz");
435        assert_eq!(format!("{buf:?}"), r#""baz""#);
436    }
437
438    #[cfg(feature = "serde")]
439    #[test]
440    fn serialize() {
441        let s: ByteString = serde_json::from_str(r#""nice bytes""#).unwrap();
442        assert_eq!(s, "nice bytes");
443    }
444
445    #[cfg(feature = "serde")]
446    #[test]
447    fn deserialize() {
448        let s = serde_json::to_string(&ByteString::from_static("nice bytes")).unwrap();
449        assert_eq!(s, r#""nice bytes""#);
450    }
451
452    #[test]
453    fn slice_ref() {
454        let string = ByteString::from_static(" foo ");
455        let subset = string.trim();
456        // subset is derived from original byte string
457        let substring = string.slice_ref(subset);
458        assert_eq!(substring, "foo");
459    }
460
461    #[test]
462    #[should_panic]
463    fn slice_ref_catches_not_a_subset() {
464        // panics because the given slice is not derived from the original byte string, despite
465        // being a logical subset of the string
466        ByteString::from_static("foo bar").slice_ref("foo");
467    }
468}