vortex_buffer/
string.rs

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