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