1#![no_std]
2#[cfg(feature = "alloc")]
3extern crate alloc;
4#[cfg(feature = "alloc")]
5use alloc::{vec::Vec};
6
7use core::{
8 fmt::{self, Display, Formatter},
9 hash::{Hash, Hasher},
10 marker::PhantomData,
11 ops::Deref,
12 str::{self, FromStr},
13};
14
15pub trait LengthPolicy {
16 fn logical_len(s: &str) -> usize;
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
20pub struct Bytes;
21impl LengthPolicy for Bytes {
22 #[inline(always)] fn logical_len(s: &str) -> usize { s.len() }
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
26pub struct Chars;
27impl LengthPolicy for Chars {
28 #[inline(always)] fn logical_len(s: &str) -> usize { s.chars().count() }
29}
30
31pub trait FormatPolicy {
32 fn check(s: &str) -> bool;
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
36pub struct AllowAll;
37impl FormatPolicy for AllowAll {
38 #[inline(always)] fn check(_: &str) -> bool { true }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
42pub struct AsciiOnly;
43impl FormatPolicy for AsciiOnly {
44 #[inline(always)] fn check(s: &str) -> bool { s.is_ascii() }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum BoundedStrError {
49 TooShort,
50 TooLong,
51 TooManyBytes,
52 InvalidContent,
53 MutationFailed,
54}
55
56pub struct BoundedStr<
57 const MIN: usize,
58 const MAX: usize,
59 const MAX_BYTES: usize,
60 L: LengthPolicy = Bytes,
61 F: FormatPolicy = AllowAll,
62> {
63 len: usize,
64 buf: [u8; MAX_BYTES],
65 #[cfg(feature = "alloc")]
66 heap_buf: Option<Vec<u8>>,
67 _marker: PhantomData<(L, F)>,
68}
69
70impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
71 BoundedStr<MIN, MAX, MAX_BYTES, L, F>
72{
73 const _CHECK: () = {
74 assert!(MIN <= MAX, "MIN must be <= MAX");
75 assert!(MAX <= MAX_BYTES, "MAX must be <= MAX_BYTES");
76 };
77
78
79 #[inline(always)]
80 pub fn len_bytes(&self) -> usize {
81 self.len
82 }
83
84 #[inline(always)]
85 pub fn len_logical(&self) -> usize {
86 L::logical_len(self.as_str())
87 }
88
89 pub fn new(s: &str) -> Result<Self, BoundedStrError> {
90 let byte_len = s.len();
91 let logical_len = L::logical_len(s);
92
93 if logical_len < MIN { return Err(BoundedStrError::TooShort); }
94 if logical_len > MAX { return Err(BoundedStrError::TooLong); }
95 if !F::check(s) { return Err(BoundedStrError::InvalidContent); }
96
97 #[cfg(feature = "alloc")]
98 if byte_len > MAX_BYTES {
99 return Ok(Self {
100 len: byte_len,
101 buf: [0u8; MAX_BYTES],
102 heap_buf: Some(s.as_bytes().to_vec()),
103 _marker: PhantomData
104 });
105 }
106
107 if byte_len > MAX_BYTES {
108 return Err(BoundedStrError::TooManyBytes);
109 }
110
111 let mut buf = [0u8; MAX_BYTES];
112 buf[..byte_len].copy_from_slice(s.as_bytes());
113
114 Ok(Self {
115 len: byte_len,
116 buf,
117 #[cfg(feature = "alloc")]
118 heap_buf: None,
119 _marker: PhantomData
120 })
121 }
122
123 pub fn mutate<Mut, R>(&mut self, mutator: Mut) -> Result<R, BoundedStrError>
124 where
125 Mut: FnOnce(&mut [u8]) -> R
126 {
127 #[cfg(feature = "alloc")]
128 if let Some(ref mut v) = self.heap_buf {
129 let mut temp = v.clone();
130 let res = mutator(&mut temp);
131 if let Ok(s) = core::str::from_utf8(&temp) {
132 let l_len = L::logical_len(s);
133 if l_len >= MIN && l_len <= MAX && F::check(s) {
134 self.len = temp.len();
135 *v = temp;
136 return Ok(res);
137 }
138 }
139 return Err(BoundedStrError::MutationFailed);
140 }
141
142 let mut temp_buf = self.buf;
143 let res = mutator(&mut temp_buf[..self.len]);
144
145 if let Ok(s) = core::str::from_utf8(&temp_buf[..self.len]) {
146 let l_len = L::logical_len(s);
147 if l_len >= MIN && l_len <= MAX && F::check(s) {
148 self.buf = temp_buf;
149 return Ok(res);
150 }
151 }
152
153 Err(BoundedStrError::MutationFailed)
154 }
155
156 #[inline(always)]
157 pub fn as_str(&self) -> &str {
158 #[cfg(feature = "alloc")]
159 if let Some(ref v) = self.heap_buf {
160 return unsafe { str::from_utf8_unchecked(v) };
161 }
162 unsafe { str::from_utf8_unchecked(&self.buf[..self.len]) }
163 }
164}
165
166
167impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
168 PartialEq for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
169{
170 fn eq(&self, other: &Self) -> bool { self.as_str() == other.as_str() }
171}
172impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
173 Eq for BoundedStr<MIN, MAX, MAX_BYTES, L, F> {}
174impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
175 PartialEq<&str> for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
176{
177 fn eq(&self, other: &&str) -> bool { self.as_str() == *other }
178}
179impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
180 Deref for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
181{
182 type Target = str;
183 fn deref(&self) -> &str { self.as_str() }
184}
185impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
186 TryFrom<&str> for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
187{
188 type Error = BoundedStrError;
189 fn try_from(s: &str) -> Result<Self, Self::Error> { Self::new(s) }
190}
191impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
192 FromStr for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
193{
194 type Err = BoundedStrError;
195 fn from_str(s: &str) -> Result<Self, Self::Err> { Self::new(s) }
196}
197impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
198 Hash for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
199{
200 fn hash<H: Hasher>(&self, state: &mut H) { self.as_str().hash(state) }
201}
202impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
203 Display for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
204{
205 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) }
206}
207impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
208 fmt::Debug for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
209{
210 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
211 f.debug_struct("BoundedStr")
212 .field("value", &self.as_str())
213 .field("len_bytes", &self.len_bytes())
214 .field("len_logical", &self.len_logical())
215 .finish()
216 }
217}
218
219#[cfg(feature = "serde")]
220impl<'de, const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
221 serde::Deserialize<'de> for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
222{
223 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224 where
225 D: serde::Deserializer<'de>,
226 {
227 let s = <&str>::deserialize(deserializer)?;
228
229 Self::new(s).map_err(|e| {
230 serde::de::Error::custom(match e {
231 BoundedStrError::TooShort => "string too short",
232 BoundedStrError::TooLong => "string too long",
233 BoundedStrError::TooManyBytes => "too many bytes for buffer",
234 BoundedStrError::InvalidContent => "invalid content format",
235 BoundedStrError::MutationFailed => "mutation failed",
236 })
237 })
238 }
239}
240
241#[cfg(feature = "serde")]
242impl<const MIN: usize, const MAX: usize, const MAX_BYTES: usize, L: LengthPolicy, F: FormatPolicy>
243 serde::Serialize for BoundedStr<MIN, MAX, MAX_BYTES, L, F>
244{
245 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
246 where
247 S: serde::Serializer,
248 {
249 serializer.serialize_str(self.as_str())
250 }
251}
252
253
254pub type StackStr<const MIN: usize, const MAX: usize, const MAXB: usize = MAX, L = Bytes, F = AllowAll> =
255 BoundedStr<MIN, MAX, MAXB, L, F>;
256
257#[cfg(feature = "alloc")]
258pub type FlexStr<const MIN: usize, const MAX: usize, const MAXB: usize = 4096, L = Bytes, F = AllowAll> =
259 BoundedStr<MIN, MAX, MAXB, L, F>;