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