vortex_buffer/
string.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::{Debug, Formatter};
5use std::ops::Deref;
6
7use vortex_error::{VortexError, vortex_err};
8
9use crate::ByteBuffer;
10
11/// A wrapper around a [`ByteBuffer`] that guarantees that the buffer contains valid UTF-8.
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct BufferString(ByteBuffer);
14
15impl BufferString {
16    /// Creates a new `BufferString` from a [`ByteBuffer`].
17    ///
18    /// # Safety
19    /// Assumes that the buffer contains valid UTF-8.
20    pub const unsafe fn new_unchecked(buffer: ByteBuffer) -> Self {
21        Self(buffer)
22    }
23
24    /// Return a view of the contents of BufferString as an immutable `&str`.
25    pub fn as_str(&self) -> &str {
26        // SAFETY: We have already validated that the buffer is valid UTF-8
27        unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) }
28    }
29
30    /// Returns the inner [`ByteBuffer`].
31    pub fn into_inner(self) -> ByteBuffer {
32        self.0
33    }
34
35    /// Returns reference to the inner [`ByteBuffer`].
36    pub fn inner(&self) -> &ByteBuffer {
37        &self.0
38    }
39}
40
41impl Debug for BufferString {
42    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
43        f.debug_struct("BufferString")
44            .field("string", &self.as_str())
45            .finish()
46    }
47}
48
49impl From<BufferString> for ByteBuffer {
50    fn from(value: BufferString) -> Self {
51        value.0
52    }
53}
54
55impl From<String> for BufferString {
56    fn from(value: String) -> Self {
57        Self(ByteBuffer::from(value.into_bytes()))
58    }
59}
60
61impl From<&str> for BufferString {
62    fn from(value: &str) -> Self {
63        Self(ByteBuffer::from(String::from(value).into_bytes()))
64    }
65}
66
67impl TryFrom<ByteBuffer> for BufferString {
68    type Error = VortexError;
69
70    fn try_from(value: ByteBuffer) -> Result<Self, Self::Error> {
71        let _ = simdutf8::basic::from_utf8(value.as_ref()).map_err(|_| {
72            #[allow(clippy::unwrap_used)]
73            // run validation using `compat` package to get more detailed error message
74            let err = simdutf8::compat::from_utf8(value.as_ref()).unwrap_err();
75            vortex_err!("invalid utf-8: {err}")
76        })?;
77        Ok(Self(value))
78    }
79}
80
81impl Deref for BufferString {
82    type Target = str;
83
84    fn deref(&self) -> &Self::Target {
85        self.as_str()
86    }
87}
88
89impl AsRef<str> for BufferString {
90    fn as_ref(&self) -> &str {
91        self.as_str()
92    }
93}
94
95impl AsRef<[u8]> for BufferString {
96    fn as_ref(&self) -> &[u8] {
97        self.as_str().as_bytes()
98    }
99}
100
101#[cfg(test)]
102mod test {
103    use crate::{Alignment, BufferString, buffer};
104
105    #[test]
106    fn buffer_string() {
107        let buf = BufferString::from("hello");
108        assert_eq!(buf.len(), 5);
109        assert_eq!(buf.into_inner().alignment(), Alignment::of::<u8>());
110    }
111
112    #[test]
113    fn buffer_string_non_ut8() {
114        assert!(BufferString::try_from(buffer![0u8, 255]).is_err());
115    }
116}