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