vex_cdc/
string.rs

1use core::{
2    borrow::{Borrow, BorrowMut},
3    ffi::CStr,
4    fmt::Display,
5    ops::{Deref, DerefMut},
6    str::FromStr,
7};
8use core::{fmt, str};
9
10use alloc::{
11    borrow::ToOwned,
12    string::{String, ToString},
13    vec,
14};
15
16use crate::{
17    decode::{Decode, DecodeError, DecodeErrorKind, DecodeWithLength},
18    encode::Encode,
19};
20
21/// A UTF-8 string with a fixed maximum capacity of `N` bytes.
22///
23/// `FixedString<N>` stores string data inline, backed by a `[u8; N]` buffer.
24/// Unlike [`String`], its capacity is fixed at compile time. The actual
25/// string length may be smaller than `N`, but may never exceed it.
26///
27/// # Invariants
28///
29/// - Contents are always valid UTF-8.
30/// - The inner string must satisfy `bytes.len() <= N`.
31/// - All bytes past the end of the string are zeroed.
32#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Hash)]
33pub struct FixedString<const N: usize>([u8; N]);
34
35impl<const N: usize> FixedString<N> {
36    /// Creates a new [`FixedString`] from the given string slice.
37    ///
38    /// Fails if the input string is larger than the fixed capacity `N`.
39    ///
40    /// # Errors
41    ///
42    /// Returns [`FixedStringSizeError`] if the string’s UTF-8 byte length
43    /// exceeds `N`.
44    pub fn new(s: impl AsRef<str>) -> Result<Self, FixedStringSizeError> {
45        let size = s.as_ref().as_bytes().len();
46
47        if size > N {
48            return Err(FixedStringSizeError {
49                input_size: size,
50                max_size: N,
51            });
52        }
53
54        // SAFETY: We have verified that s.as_bytes().len() <= N above.
55        Ok(unsafe { Self::new_unchecked(s) })
56    }
57
58    /// Creates a new [`FixedString`] without checking the size.
59    ///
60    /// If the input string is longer than `N` bytes, it will be truncated
61    /// to fit into the buffer.
62    ///
63    /// # Safety
64    ///
65    /// The caller must ensure that `s` is valid UTF-8 and that truncation
66    /// does not violate invariants of how the string is later used.
67    ///
68    /// Normally you should prefer [`FixedString::new`], which enforces the
69    /// size bound at runtime.
70    pub unsafe fn new_unchecked(s: impl AsRef<str>) -> Self {
71        let s = s.as_ref();
72        let bytes = s.as_bytes();
73        let len = bytes.len().min(N); // truncate if necessary
74
75        let mut buf = [0; N];
76        buf[..len].copy_from_slice(&bytes[..len]);
77
78        Self(buf)
79    }
80
81    /// Extracts a string slice containing this string's contents.
82    pub fn as_str(&self) -> &str {
83        let len = self.0.iter().position(|&b| b == 0).unwrap_or(N);
84
85        // SAFETY: Construction guarantees valid UTF-8 up to `len`.
86        unsafe { str::from_utf8_unchecked(&self.0[..len]) }
87    }
88
89    /// Converts a `FixedString` into a mutable string slice.
90    pub fn as_mut_str(&mut self) -> &mut str {
91        let len = self.0.iter().position(|&b| b == 0).unwrap_or(N);
92
93        unsafe { str::from_utf8_unchecked_mut(&mut self.0[..len]) }
94    }
95}
96
97impl<const N: usize> Deref for FixedString<N> {
98    type Target = str;
99
100    fn deref(&self) -> &str {
101        self.as_str()
102    }
103}
104
105impl<const N: usize> DerefMut for FixedString<N> {
106    fn deref_mut(&mut self) -> &mut str {
107        self.as_mut_str()
108    }
109}
110
111impl<const N: usize> Default for FixedString<N> {
112    fn default() -> Self {
113        Self([0; N])
114    }
115}
116
117impl<const N: usize> AsRef<str> for FixedString<N> {
118    fn as_ref(&self) -> &str {
119        self
120    }
121}
122
123impl<const N: usize> AsMut<str> for FixedString<N> {
124    fn as_mut(&mut self) -> &mut str {
125        self
126    }
127}
128
129impl<const N: usize> Borrow<str> for FixedString<N> {
130    #[inline]
131    fn borrow(&self) -> &str {
132        &self[..]
133    }
134}
135
136impl<const N: usize> BorrowMut<str> for FixedString<N> {
137    #[inline]
138    fn borrow_mut(&mut self) -> &mut str {
139        &mut self[..]
140    }
141}
142
143impl<const N: usize> AsRef<[u8]> for FixedString<N> {
144    #[inline]
145    fn as_ref(&self) -> &[u8] {
146        self.as_bytes()
147    }
148}
149
150impl<const N: usize> TryFrom<&str> for FixedString<N> {
151    type Error = FixedStringSizeError;
152
153    fn try_from(value: &str) -> Result<FixedString<N>, FixedStringSizeError> {
154        Self::new(value)
155    }
156}
157
158impl<const N: usize> TryFrom<&mut str> for FixedString<N> {
159    type Error = FixedStringSizeError;
160
161    fn try_from(value: &mut str) -> Result<FixedString<N>, FixedStringSizeError> {
162        Self::new(value)
163    }
164}
165
166impl<const N: usize> FromStr for FixedString<N> {
167    type Err = FixedStringSizeError;
168
169    fn from_str(s: &str) -> Result<Self, Self::Err> {
170        Self::new(s.to_string())
171    }
172}
173
174impl<const N: usize> Display for FixedString<N> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        self.as_str().fmt(f)
177    }
178}
179
180impl<const N: usize> Encode for FixedString<N> {
181    fn size(&self) -> usize {
182        N + 1
183    }
184
185    fn encode(&self, data: &mut [u8]) {
186        let data_len = self.0.len();
187
188        data[..data_len].copy_from_slice(self.as_bytes());
189        data[data_len + 1] = 0; // Null terminator
190    }
191}
192
193impl<const N: usize> Decode for FixedString<N> {
194    fn decode(data: &mut &[u8]) -> Result<Self, DecodeError> {
195        Ok(unsafe { Self::new_unchecked(String::decode_with_len(data, N)?) })
196    }
197}
198
199/// Returned when a [`FixedString`] cannot fit the specified string.
200#[derive(Clone, PartialEq, Eq, Debug)]
201pub struct FixedStringSizeError {
202    pub input_size: usize,
203    pub max_size: usize,
204}
205
206impl fmt::Display for FixedStringSizeError {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(
209            f,
210            "string with size {} exceeds the maximum size of FixedString<{}>",
211            self.input_size, self.max_size
212        )
213    }
214}
215
216impl core::error::Error for FixedStringSizeError {
217    fn description(&self) -> &str {
218        "string exceeds the maximum size of FixedString"
219    }
220}
221
222impl Encode for &str {
223    fn size(&self) -> usize {
224        self.len() + 1 // +1 for null terminator
225    }
226
227    fn encode(&self, data: &mut [u8]) {
228        let bytes = self.as_bytes();
229
230        data[..bytes.len()].copy_from_slice(bytes);
231        data[bytes.len()] = 0;
232    }
233}
234
235impl Encode for String {
236    fn size(&self) -> usize {
237        self.as_str().size()
238    }
239
240    fn encode(&self, data: &mut [u8]) {
241        self.as_str().encode(data)
242    }
243}
244
245impl DecodeWithLength for String {
246    fn decode_with_len(data: &mut &[u8], len: usize) -> Result<Self, DecodeError> {
247        let max_size = len as _;
248
249        let mut utf8 = vec![0u8; max_size];
250        for (i, string_byte) in utf8.iter_mut().enumerate() {
251            let byte = u8::decode(data)?;
252
253            if i == max_size {
254                if byte != 0 {
255                    return Err(DecodeError::new::<Self>(
256                        DecodeErrorKind::UnterminatedString,
257                    ));
258                }
259                break;
260            }
261            if byte == 0 {
262                break;
263            }
264
265            *string_byte = byte;
266        }
267
268        let cstr = CStr::from_bytes_until_nul(&utf8)
269            .map_err(|_| DecodeError::new::<Self>(DecodeErrorKind::UnterminatedString))?;
270
271        Ok(cstr
272            .to_str()
273            .map_err(|e| DecodeError::new::<Self>(e.into()))?
274            .to_owned())
275    }
276}