Skip to main content

bounded_str/
lib.rs

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>;