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    #[inline]
85    fn deref(&self) -> &Self::Target {
86        self.as_str()
87    }
88}
89
90impl AsRef<str> for BufferString {
91    #[inline]
92    fn as_ref(&self) -> &str {
93        self.as_str()
94    }
95}
96
97impl AsRef<[u8]> for BufferString {
98    #[inline]
99    fn as_ref(&self) -> &[u8] {
100        self.as_str().as_bytes()
101    }
102}
103
104#[cfg(test)]
105mod test {
106    use crate::{Alignment, BufferString, buffer};
107
108    #[test]
109    fn buffer_string() {
110        let buf = BufferString::from("hello");
111        assert_eq!(buf.len(), 5);
112        assert_eq!(buf.into_inner().alignment(), Alignment::of::<u8>());
113    }
114
115    #[test]
116    fn buffer_string_non_ut8() {
117        assert!(BufferString::try_from(buffer![0u8, 255]).is_err());
118    }
119}