1#![no_std]
4#![deny(missing_docs)]
5
6#[cfg(any(feature = "alloc", test))]
7extern crate alloc;
8
9use core::fmt;
10use core::marker::PhantomData;
11use core::ops::Range;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct CapacityError;
16
17impl fmt::Display for CapacityError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 f.write_str("fixed-capacity buffer is full")
20 }
21}
22
23impl core::error::Error for CapacityError {}
24
25#[derive(Clone, Copy, PartialEq, Eq)]
27pub struct FixedBytes<const N: usize> {
28 data: [u8; N],
29 len: usize,
30}
31
32impl<const N: usize> FixedBytes<N> {
33 pub const fn new() -> Self {
35 Self {
36 data: [0; N],
37 len: 0,
38 }
39 }
40
41 pub const fn empty() -> Self {
43 Self::new()
44 }
45
46 pub const fn with_size(size: usize) -> Self {
51 assert!(size <= N);
52 Self {
53 data: [0; N],
54 len: size,
55 }
56 }
57
58 pub const fn len(&self) -> usize {
60 self.len
61 }
62
63 pub const fn is_empty(&self) -> bool {
65 self.len == 0
66 }
67
68 pub const fn capacity(&self) -> usize {
70 N
71 }
72
73 pub const fn remaining_capacity(&self) -> usize {
75 N - self.len
76 }
77
78 pub fn as_bytes(&self) -> &[u8] {
80 &self.data[..self.len]
81 }
82
83 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
85 &mut self.data[..self.len]
86 }
87
88 pub fn try_as_str(&self) -> Result<&str, core::str::Utf8Error> {
90 core::str::from_utf8(self.as_bytes())
91 }
92
93 pub fn as_str(&self) -> &str {
98 self.try_as_str()
99 .expect("FixedBytes contains invalid UTF-8")
100 }
101
102 pub fn clear(&mut self) {
104 self.len = 0;
105 }
106
107 pub fn truncate(&mut self, new_len: usize) {
112 assert!(new_len <= self.len);
113 self.len = new_len;
114 }
115
116 pub fn allocate(&mut self, bytes: usize) {
121 assert!(bytes <= self.remaining_capacity());
122 self.len += bytes;
123 }
124
125 pub fn try_from_slice(value: &[u8]) -> Result<Self, CapacityError> {
127 let mut result = Self::new();
128 result.try_push_slice(value)?;
129 Ok(result)
130 }
131
132 pub fn try_push_slice(&mut self, value: &[u8]) -> Result<Range<usize>, CapacityError> {
134 if value.len() > self.remaining_capacity() {
135 return Err(CapacityError);
136 }
137 let start = self.len;
138 self.len += value.len();
139 self.data[start..self.len].copy_from_slice(value);
140 Ok(start..self.len)
141 }
142
143 pub fn push_slice(&mut self, value: &[u8]) -> Range<usize> {
148 self.try_push_slice(value)
149 .expect("FixedBytes capacity exceeded")
150 }
151
152 pub fn try_push_byte(&mut self, value: u8) -> Result<usize, CapacityError> {
154 if self.len == N {
155 return Err(CapacityError);
156 }
157 let index = self.len;
158 self.data[index] = value;
159 self.len += 1;
160 Ok(index)
161 }
162
163 pub fn push_byte(&mut self, value: u8) -> usize {
168 self.try_push_byte(value)
169 .expect("FixedBytes capacity exceeded")
170 }
171}
172
173impl<const N: usize> Default for FixedBytes<N> {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl<const N: usize> From<&[u8]> for FixedBytes<N> {
180 fn from(value: &[u8]) -> Self {
184 Self::try_from_slice(value).expect("FixedBytes capacity exceeded")
185 }
186}
187
188impl<const N: usize> From<&[u8; N]> for FixedBytes<N> {
189 fn from(value: &[u8; N]) -> Self {
190 Self {
191 data: *value,
192 len: N,
193 }
194 }
195}
196
197impl<const N: usize> fmt::Debug for FixedBytes<N> {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 f.debug_tuple("FixedBytes").field(&self.as_bytes()).finish()
200 }
201}
202
203impl<const N: usize> fmt::Display for FixedBytes<N> {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self.try_as_str() {
206 Ok(value) => f.write_str(value),
207 Err(_) => write!(f, "{:?}", self.as_bytes()),
208 }
209 }
210}
211
212#[derive(Clone, Copy, PartialEq, Eq)]
214pub struct FixedStr<const N: usize>(FixedBytes<N>);
215
216impl<const N: usize> FixedStr<N> {
217 pub const fn new() -> Self {
219 Self(FixedBytes::new())
220 }
221
222 pub const fn len(&self) -> usize {
224 self.0.len()
225 }
226
227 pub const fn is_empty(&self) -> bool {
229 self.0.is_empty()
230 }
231
232 pub const fn capacity(&self) -> usize {
234 N
235 }
236
237 pub const fn remaining_capacity(&self) -> usize {
239 self.0.remaining_capacity()
240 }
241
242 pub fn as_str(&self) -> &str {
244 unsafe { core::str::from_utf8_unchecked(self.0.as_bytes()) }
246 }
247
248 pub fn as_bytes(&self) -> &[u8] {
250 self.0.as_bytes()
251 }
252
253 pub fn clear(&mut self) {
255 self.0.clear();
256 }
257
258 pub fn try_push_str(&mut self, value: &str) -> Result<Range<usize>, CapacityError> {
260 self.0.try_push_slice(value.as_bytes())
261 }
262
263 pub fn push_str(&mut self, value: &str) -> Range<usize> {
268 self.try_push_str(value)
269 .expect("FixedStr capacity exceeded")
270 }
271
272 pub fn try_push(&mut self, value: char) -> Result<Range<usize>, CapacityError> {
274 let mut bytes = [0; 4];
275 self.try_push_str(value.encode_utf8(&mut bytes))
276 }
277
278 pub fn truncate(&mut self, new_len: usize) {
283 assert!(self.as_str().is_char_boundary(new_len));
284 self.0.truncate(new_len);
285 }
286
287 pub const fn into_bytes(self) -> FixedBytes<N> {
289 self.0
290 }
291}
292
293impl<const N: usize> Default for FixedStr<N> {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299impl<const N: usize> TryFrom<&str> for FixedStr<N> {
300 type Error = CapacityError;
301
302 fn try_from(value: &str) -> Result<Self, Self::Error> {
303 Ok(Self(FixedBytes::try_from_slice(value.as_bytes())?))
304 }
305}
306
307impl<const N: usize> TryFrom<FixedBytes<N>> for FixedStr<N> {
308 type Error = core::str::Utf8Error;
309
310 fn try_from(value: FixedBytes<N>) -> Result<Self, Self::Error> {
311 value.try_as_str()?;
312 Ok(Self(value))
313 }
314}
315
316impl<const N: usize> fmt::Debug for FixedStr<N> {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 f.debug_tuple("FixedStr").field(&self.as_str()).finish()
319 }
320}
321
322impl<const N: usize> fmt::Display for FixedStr<N> {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.write_str(self.as_str())
325 }
326}
327
328pub trait Utf16ByteOrder: Copy {
330 fn read(bytes: [u8; 2]) -> u16;
332 fn write(value: u16) -> [u8; 2];
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
338pub struct LittleEndian;
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub struct BigEndian;
343
344impl Utf16ByteOrder for LittleEndian {
345 fn read(bytes: [u8; 2]) -> u16 {
346 u16::from_le_bytes(bytes)
347 }
348 fn write(value: u16) -> [u8; 2] {
349 value.to_le_bytes()
350 }
351}
352
353impl Utf16ByteOrder for BigEndian {
354 fn read(bytes: [u8; 2]) -> u16 {
355 u16::from_be_bytes(bytes)
356 }
357 fn write(value: u16) -> [u8; 2] {
358 value.to_be_bytes()
359 }
360}
361
362#[repr(C)]
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub struct FixedUtf16<const N: usize, E: Utf16ByteOrder> {
366 data: [[u8; 2]; N],
367 byte_order: PhantomData<E>,
368}
369
370pub type FixedUtf16Le<const N: usize> = FixedUtf16<N, LittleEndian>;
372pub type FixedUtf16Be<const N: usize> = FixedUtf16<N, BigEndian>;
374
375impl<const N: usize, E: Utf16ByteOrder> FixedUtf16<N, E> {
376 pub const fn new() -> Self {
378 Self {
379 data: [[0; 2]; N],
380 byte_order: PhantomData,
381 }
382 }
383
384 pub fn try_from_str(value: &str) -> Result<Self, CapacityError> {
386 let mut result = Self::new();
387 for (index, unit) in value.encode_utf16().enumerate() {
388 if index == N {
389 return Err(CapacityError);
390 }
391 result.data[index] = E::write(unit);
392 }
393 Ok(result)
394 }
395
396 pub fn as_bytes(&self) -> &[[u8; 2]; N] {
398 &self.data
399 }
400
401 pub fn decode(&self) -> impl Iterator<Item = Result<char, core::char::DecodeUtf16Error>> + '_ {
403 char::decode_utf16(
404 self.data
405 .iter()
406 .map(|bytes| E::read(*bytes))
407 .take_while(|unit| *unit != 0),
408 )
409 }
410
411 #[cfg(feature = "alloc")]
412 pub fn to_string(&self) -> Result<alloc::string::String, core::char::DecodeUtf16Error> {
414 self.decode().collect()
415 }
416}
417
418impl<const N: usize, E: Utf16ByteOrder> Default for FixedUtf16<N, E> {
419 fn default() -> Self {
420 Self::new()
421 }
422}
423
424#[cfg(feature = "bytemuck")]
425unsafe impl<const N: usize, E: Utf16ByteOrder + 'static> bytemuck::Zeroable for FixedUtf16<N, E> {}
426#[cfg(feature = "bytemuck")]
427unsafe impl<const N: usize, E: Utf16ByteOrder + 'static> bytemuck::Pod for FixedUtf16<N, E> {}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[test]
434 fn bytes_allow_non_utf8() {
435 let bytes = FixedBytes::<2>::try_from_slice([0xff, 0].as_slice()).unwrap();
436 assert!(bytes.try_as_str().is_err());
437 }
438
439 #[test]
440 fn fixed_str_preserves_utf8() {
441 let mut text = FixedStr::<8>::try_from("é").unwrap();
442 text.try_push('!').unwrap();
443 assert_eq!(text.as_str(), "é!");
444 }
445
446 #[test]
447 fn utf16_round_trips_both_orders() {
448 let le = FixedUtf16Le::<8>::try_from_str("A😀").unwrap();
449 let be = FixedUtf16Be::<8>::try_from_str("A😀").unwrap();
450 assert_eq!(le.to_string().unwrap(), "A😀");
451 assert_eq!(be.to_string().unwrap(), "A😀");
452 }
453}