1use core::fmt;
2use core::str;
3
4#[derive(Clone, PartialEq, Eq)]
26pub struct BufStr<const N: usize> {
27 pub bytes: [u8; N],
28}
29
30impl<const N: usize> Default for BufStr<N> {
31 #[inline(always)]
32 fn default() -> Self {
33 Self { bytes: [0; N] }
34 }
35}
36
37impl<const N: usize> BufStr<N> {
38 pub const SIZE: usize = N;
39
40 #[inline]
45 pub fn new(s: &str) -> Self {
46 let mut bytes = [0u8; N];
47 copy_valid_utf8_prefix(&mut bytes, s.as_bytes(), N);
48 Self { bytes }
49 }
50
51 #[inline]
58 pub fn from_bytes(bytes: &[u8]) -> Self {
59 let mut arr = [0u8; N];
60 let len = bytes.len().min(N);
61 arr[..len].copy_from_slice(&bytes[..len]);
62 Self { bytes: arr }
63 }
64
65 pub fn as_str(&self) -> &str {
74 let slice = &self.bytes[..self
75 .bytes
76 .iter()
77 .position(|&b| b == 0)
78 .unwrap_or(Self::SIZE)];
79 match str::from_utf8(slice) {
80 Ok(s) => s,
81 Err(e) => handle_invalid_utf8(slice, e),
82 }
83 }
84
85 pub fn as_bytes(&self) -> &[u8] {
87 &self.bytes[..self
88 .bytes
89 .iter()
90 .position(|&b| b == 0)
91 .unwrap_or(Self::SIZE)]
92 }
93}
94
95impl<const N: usize> fmt::Write for BufStr<N> {
96 #[inline(never)]
97 fn write_str(&mut self, s: &str) -> fmt::Result {
98 let current = self.as_bytes().len();
99 let remaining = N.saturating_sub(current);
100 if remaining == 0 {
101 return Ok(());
102 }
103
104 copy_valid_utf8_prefix(&mut self.bytes[current..], s.as_bytes(), remaining);
105 Ok(())
106 }
107}
108
109impl<const N: usize> fmt::Display for BufStr<N> {
110 #[inline(always)]
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str(self.as_str())
113 }
114}
115
116impl<const N: usize> fmt::Debug for BufStr<N> {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{:?}", self.as_str())
119 }
120}
121
122#[cfg(feature = "serde")]
123impl<const N: usize> serde::Serialize for BufStr<N> {
124 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
125 where
126 S: serde::Serializer,
127 {
128 self.as_str().serialize(serializer)
129 }
130}
131
132#[cfg(feature = "serde")]
133impl<'de, const N: usize> serde::Deserialize<'de> for BufStr<N> {
134 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135 where
136 D: serde::Deserializer<'de>,
137 {
138 let s: &str = serde::Deserialize::deserialize(deserializer)?;
139 Ok(BufStr::new(s))
140 }
141}
142
143#[cfg(feature = "defmt")]
144impl<const N: usize> defmt::Format for BufStr<N> {
145 fn format(&self, f: defmt::Formatter) {
146 defmt::write!(f, "{}", self.as_str());
147 }
148}
149
150#[inline(never)]
151fn copy_valid_utf8_prefix(dst: &mut [u8], src: &[u8], max_len: usize) -> usize {
152 let len = src.len().min(max_len);
153 match str::from_utf8(&src[..len]) {
154 Ok(_) => {
155 dst[..len].copy_from_slice(&src[..len]);
156 len
157 }
158 Err(e) => {
159 let valid = e.valid_up_to();
160 dst[..valid].copy_from_slice(&src[..valid]);
161 valid
162 }
163 }
164}
165
166#[cold]
167#[inline(never)]
168fn handle_invalid_utf8(slice: &[u8], e: core::str::Utf8Error) -> &str {
169 let valid = e.valid_up_to();
170 if valid == 0 {
171 "\u{FFFD}"
172 } else {
173 str::from_utf8(&slice[..valid]).unwrap_or("\u{FFFD}")
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn as_str_valid() {
183 assert_eq!(BufStr::<16>::new("hello").as_str(), "hello");
184 assert_eq!(BufStr::<8>::default().as_str(), "");
185 }
186
187 #[test]
188 fn as_str_invalid_leading_byte() {
189 let s = BufStr::<8>::from_bytes(&[0xFF, b'a']);
190 assert_eq!(s.as_str(), "\u{FFFD}");
191 }
192
193 #[test]
194 fn as_str_valid_prefix_then_garbage() {
195 let s = BufStr::<8>::from_bytes(&[b'h', b'i', 0xFF, b'!']);
196 assert_eq!(s.as_str(), "hi");
197 }
198
199 #[test]
200 fn as_str_truncated_multibyte_at_start() {
201 let s = BufStr::<8>::from_bytes(&[0xE2, 0x82]);
203 assert_eq!(s.as_str(), "\u{FFFD}");
204 }
205
206 #[test]
207 fn as_str_truncated_multibyte_after_valid_prefix() {
208 let s = BufStr::<8>::from_bytes(&[b'h', b'i', 0xE2, 0x82]);
209 assert_eq!(s.as_str(), "hi");
210 }
211
212 #[test]
213 fn as_str_stops_at_nul() {
214 let s = BufStr::<8>::from_bytes(b"ab\0cd");
215 assert_eq!(s.as_str(), "ab");
216 assert_eq!(s.as_bytes(), b"ab");
217 }
218}