1use core::fmt::{self};
2use core::str;
3
4#[derive(Clone, Copy, PartialEq, Eq)]
18pub struct LiteStr<const N: usize> {
19 bytes: [u8; N],
20}
21
22impl<const N: usize> Default for LiteStr<N> {
23 #[inline(always)]
24 fn default() -> Self {
25 Self { bytes: [0; N] }
26 }
27}
28
29impl<const N: usize> LiteStr<N> {
30 pub const SIZE: usize = N;
31
32 #[inline(never)]
37 pub fn new(s: &str) -> Self {
38 let mut bytes = [0u8; N];
39 copy_valid_utf8_prefix(&mut bytes, s.as_bytes(), N);
40 Self { bytes }
41 }
42
43 #[inline(always)]
45 pub fn as_str(&self) -> Result<&str, LiteStrErr> {
46 let end = find_first_nul(&self.bytes);
47 str::from_utf8(&self.bytes[..end]).map_err(|_| LiteStrErr::CorruptedData)
48 }
49
50 #[inline(always)]
57 pub fn from_bytes(bytes: &[u8]) -> Self {
58 let mut arr = [0u8; N];
59 let len = bytes.len().min(N);
60 arr[..len].copy_from_slice(&bytes[..len]);
61 Self { bytes: arr }
62 }
63
64 #[inline(always)]
65 pub fn to_bytes(&self) -> [u8; N] {
66 self.bytes
67 }
68
69 #[inline(always)]
71 pub fn as_bytes(&self) -> &[u8] {
72 &self.bytes[..find_first_nul(&self.bytes)]
73 }
74
75 #[allow(clippy::len_without_is_empty)]
77 #[inline(always)]
78 pub fn len(&self) -> usize {
79 find_first_nul(&self.bytes)
80 }
81}
82
83impl<const N: usize> fmt::Write for LiteStr<N> {
84 #[inline(never)]
85 fn write_str(&mut self, s: &str) -> fmt::Result {
86 let current = self.len();
87 let remaining = N.saturating_sub(current);
88 if remaining == 0 {
89 return Ok(());
90 }
91
92 copy_valid_utf8_prefix(&mut self.bytes[current..], s.as_bytes(), remaining);
93 Ok(())
94 }
95}
96
97impl<const N: usize> fmt::Debug for LiteStr<N> {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self.as_str() {
100 Ok(s) => write!(f, "{:?}", s),
101 Err(_) => f.write_str("LiteStr(<invalid utf-8>)"),
102 }
103 }
104}
105
106#[cfg(feature = "serde")]
107impl<const N: usize> serde::Serialize for LiteStr<N> {
108 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109 where
110 S: serde::Serializer,
111 {
112 self.as_str()
113 .map_err(serde::ser::Error::custom)?
114 .serialize(serializer)
115 }
116}
117
118#[cfg(feature = "serde")]
119impl<'de, const N: usize> serde::Deserialize<'de> for LiteStr<N> {
120 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
121 where
122 D: serde::Deserializer<'de>,
123 {
124 let s: &str = serde::Deserialize::deserialize(deserializer)?;
125 Ok(LiteStr::new(s))
126 }
127}
128
129#[inline(never)]
130fn find_first_nul(bytes: &[u8]) -> usize {
131 bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len())
132}
133
134#[inline(never)]
135fn copy_valid_utf8_prefix(dst: &mut [u8], src: &[u8], max_len: usize) -> usize {
136 let len = src.len().min(max_len);
137 match str::from_utf8(&src[..len]) {
138 Ok(_) => {
139 dst[..len].copy_from_slice(&src[..len]);
140 len
141 }
142 Err(e) => {
143 let valid = e.valid_up_to();
144 dst[..valid].copy_from_slice(&src[..valid]);
145 valid
146 }
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum LiteStrErr {
153 CorruptedData,
155}
156
157impl fmt::Display for LiteStrErr {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 LiteStrErr::CorruptedData => f.write_str("content is not valid UTF-8"),
161 }
162 }
163}
164
165impl core::error::Error for LiteStrErr {}