Skip to main content

g_string/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4mod conversion;
5mod equality;
6mod error;
7mod iterator;
8mod macros;
9mod mutation;
10mod order;
11mod query;
12pub use query::Pattern;
13
14mod pattern_impl;
15
16#[cfg(feature = "secret")]
17mod gsecret;
18
19#[cfg(feature = "secret")]
20pub use gsecret::GSecret;
21
22#[cfg(feature = "grapheme")]
23pub use unicode_segmentation::{Graphemes, UnicodeSegmentation};
24
25#[cfg(feature = "serde")]
26mod serde;
27
28pub use error::GStringError;
29
30use error::Err;
31
32use core::{
33    convert::Infallible,
34    error::Error,
35    fmt::{Debug, Display},
36    marker::PhantomData,
37};
38
39/// Default value of lower bound.
40pub const DEFAULT_MIN: usize = 0;
41
42/// Default value of upper bound.
43pub const DEFAULT_MAX: usize = 255;
44
45/// Default value is ASCII only or not.
46pub const DEFAULT_ASCII_ONLY: bool = false;
47
48/// GString alias without validation.
49pub type GStringNV<const MIN: usize, const MAX: usize, const ASCII_ONLY: bool> =
50    GString<NoValidation, MIN, MAX, ASCII_ONLY>;
51
52/// Validation trait
53///
54/// Usually it's implemented by marker type.
55pub trait Validator: Clone {
56    type Err: Error + Send + Sync + 'static;
57
58    /// Validate the string.
59    fn validate(s: impl AsRef<str>) -> Result<(), Self::Err>;
60}
61
62/// Mark validation allowing empty string.
63///
64/// This will enable `Default` impl and `clear()` method provided that MIN == 0.
65pub trait AllowEmpty {}
66
67/// Validator implementation without validation.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct NoValidation;
70
71impl Validator for NoValidation {
72    type Err = Infallible;
73
74    fn validate(_: impl AsRef<str>) -> Result<(), Self::Err> {
75        Ok(())
76    }
77}
78
79impl Validator for () {
80    type Err = Infallible;
81
82    fn validate(_: impl AsRef<str>) -> Result<(), Self::Err> {
83        Ok(())
84    }
85}
86
87impl AllowEmpty for NoValidation {}
88
89impl AllowEmpty for () {}
90
91/// `GString` contains stack-allocated, Copy, bounded string along with ASCII toggle and embedded validation logic.
92///
93/// # Generic parameters:
94/// - `V: Validator`: default to [`NoValidation`], it will validate the string upon creation and deserialization.
95/// - `const MIN: usize`: default to [`DEFAULT_MIN`], in bytes, determine minimum length.
96/// - `const MAX: usize`: default to [`DEFAULT_MAX`], in bytes, determine maximum length.
97/// - `const ASCII_ONLY: bool`: default to [`DEFAULT_ASCII_ONLY`], determine whether GString may contains arbitrary or ASCII only UTF-8 encoded string.
98///
99/// # Usage
100/// You can use this type in several ways:
101/// - Defaulted to default generic params: `let a: GString = GString::try_new("anjay!!").unwrap()`.
102/// - Defaulted to default generic params with `try_default(...)`: `let a = GString::try_default("anjay!!").unwrap()`.
103/// - Using type aliases. You can declare some aliases matching the behavior you want: `type Username = GString<UsernameValidation, 3, 20, true>`.
104/// - Declaring each generic params from left to right. Declaration must be from left to right with omission allowed on right-most params: `let a = GString::<(), 2>::try_new("anjay!!").unwrap()`.
105///
106/// # Examples
107/// ```rust
108/// use g_string::{GString, Validator, GStringError};
109///
110/// let a: GString = GString::try_new("anjay!!").unwrap();
111/// assert_eq!(a, "anjay!!");
112///
113/// let a = GString::try_default("anjay!!").unwrap();
114/// assert_eq!(a, "anjay!!");
115///
116/// #[derive(Debug, Clone)]
117/// struct UsernameValidation;
118///
119/// impl Validator for UsernameValidation {
120///     type Err = GStringError<&'static str>;
121///
122///     fn validate(_: impl AsRef<str>) -> Result<(), Self::Err> {
123///         // some validation logics here...
124///         Ok(())
125///     }   
126/// }
127///
128/// type Username = GString<UsernameValidation, 3, 20, true>;
129/// let a = Username::try_new("wanjay!!🤣");
130/// assert!(a.is_err()); // because '🤣' is not ASCII.
131///
132/// let a = GString::<(), 2, 4>::try_new("anjay!!");
133/// assert!(a.is_err()); // MAX is 4.
134/// ```
135#[derive(Copy, Clone, Eq)]
136pub struct GString<
137    V: Validator = NoValidation,
138    const MIN: usize = DEFAULT_MIN,
139    const MAX: usize = DEFAULT_MAX,
140    const ASCII_ONLY: bool = DEFAULT_ASCII_ONLY,
141> {
142    buf: [u8; MAX],
143    len: usize,
144    _validator: PhantomData<V>,
145}
146
147impl GString {
148    /// Construct with default generic params.
149    ///
150    /// # Examples
151    /// ```rust
152    /// use g_string::GString;
153    ///
154    /// let a = GString::try_default("anjay!!").unwrap();
155    /// assert_eq!(a, "anjay!!");
156    /// ```
157    #[inline]
158    pub fn try_default<S>(s: S) -> Result<Self, GStringError<Infallible>>
159    where
160        S: AsRef<str>,
161    {
162        Self::try_new(s)
163    }
164}
165
166impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
167    GString<V, MIN, MAX, ASCII_ONLY>
168{
169    /// Construct new GString. All invariants from generic params will be imposed here.
170    ///
171    /// # Examples
172    /// ```rust
173    /// use g_string::GString;
174    ///
175    /// let a: GString = GString::try_new("anjay!!").unwrap();
176    /// assert_eq!(a, "anjay!!");
177    /// ```
178    #[inline]
179    pub fn try_new<S>(s: S) -> Result<Self, GStringError<V::Err>>
180    where
181        S: AsRef<str>,
182    {
183        let gstring = Self::stack_allocate(s.as_ref())?;
184
185        gstring.check_bounds()?;
186        gstring.check_ascii()?;
187
188        gstring.validate()
189    }
190
191    // Allocate `s` in stack without validations(yet).
192    const fn stack_allocate(s: &str) -> Result<Self, Err> {
193        let bytes = s.as_bytes();
194        let len = bytes.len();
195
196        if len > MAX {
197            return Err(Err::TooLong(MAX));
198        }
199
200        let mut buf = [0u8; MAX];
201        let mut i = 0;
202        while i < len {
203            buf[i] = bytes[i];
204            i += 1;
205        }
206
207        Ok(Self {
208            buf,
209            len,
210            _validator: PhantomData,
211        })
212    }
213
214    // Check upper and lower bounds.
215    #[inline]
216    const fn check_bounds(&self) -> Result<(), Err> {
217        const {
218            assert!(MIN <= MAX, "MIN cannot be bigger than MAX");
219        }
220        if self.len < MIN {
221            return Err(Err::TooShort(MIN));
222        }
223        if self.len > MAX {
224            return Err(Err::TooLong(MAX));
225        }
226
227        Ok(())
228    }
229
230    // Check whether ASCII only met.
231    #[inline]
232    const fn check_ascii(&self) -> Result<(), Err> {
233        if ASCII_ONLY {
234            let mut i = 0;
235            while i < self.len {
236                // If a byte is >= 128, it's a multi-byte UTF-8 character (not ASCII)
237                if self.buf[i] >= 128 {
238                    return Err(Err::NotAscii);
239                }
240                i += 1;
241            }
242        }
243
244        Ok(())
245    }
246
247    // Execute validation logic.
248    #[inline(always)]
249    pub fn validate(self) -> Result<Self, GStringError<V::Err>> {
250        V::validate(&self).map_err(GStringError::Validation)?;
251        Ok(self)
252    }
253}
254
255/// GString from const generic constructor.
256///
257/// It must be validated before getting actual `GString`.
258#[derive(Debug)]
259pub struct InValidatedGString<
260    V: Validator,
261    const MIN: usize,
262    const MAX: usize,
263    const ASCII_ONLY: bool,
264>(GString<V, MIN, MAX, ASCII_ONLY>);
265
266impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
267    InValidatedGString<V, MIN, MAX, ASCII_ONLY>
268{
269    /// Validate into GString.
270    #[inline(always)]
271    pub fn validate(self) -> Result<GString<V, MIN, MAX, ASCII_ONLY>, GStringError<V::Err>> {
272        self.0.validate()
273    }
274}
275
276macro_rules! errpanic {
277    ($expr:expr) => {
278        match $expr {
279            Ok(v) => v,
280            Err(Err::TooShort(_)) => {
281                panic!("minimum length below MIN")
282            }
283            Err(Err::TooLong(_)) => {
284                panic!("maximum length exceeds MAX")
285            }
286            Err(Err::NotAscii) => {
287                panic!("only ASCII characters are allowed")
288            }
289        }
290    };
291}
292
293impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
294    GString<V, MIN, MAX, ASCII_ONLY>
295{
296    /// Construct GString in const context returning invalidated string.
297    /// Validate the return to get GString.
298    #[allow(clippy::new_ret_no_self)]
299    pub const fn new(s: &str) -> InValidatedGString<V, MIN, MAX, ASCII_ONLY> {
300        let ret = errpanic!(Self::stack_allocate(s));
301        errpanic!(ret.check_bounds());
302        errpanic!(ret.check_ascii());
303        InValidatedGString(ret)
304    }
305}
306
307impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool> Display
308    for GString<V, MIN, MAX, ASCII_ONLY>
309{
310    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
311        write!(f, "{}", self.as_str())
312    }
313}
314
315impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool> Debug
316    for GString<V, MIN, MAX, ASCII_ONLY>
317{
318    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
319        write!(
320            f,
321            "GString(\"{}\", MIN={}, MAX={}, ASCII_ONLY={})",
322            self.as_str(),
323            MIN,
324            MAX,
325            ASCII_ONLY
326        )
327    }
328}
329
330impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
331    core::borrow::Borrow<str> for GString<V, MIN, MAX, ASCII_ONLY>
332{
333    #[inline]
334    fn borrow(&self) -> &str {
335        self.as_str()
336    }
337}
338
339impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool> core::hash::Hash
340    for GString<V, MIN, MAX, ASCII_ONLY>
341{
342    #[inline]
343    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
344        self.as_str().hash(state)
345    }
346}
347
348impl<V: Validator + AllowEmpty, const MAX: usize, const ASCII_ONLY: bool> Default
349    for GString<V, 0, MAX, ASCII_ONLY>
350{
351    #[inline]
352    fn default() -> Self {
353        Self {
354            buf: [0u8; MAX],
355            len: 0,
356            _validator: PhantomData,
357        }
358    }
359}
360
361#[cfg(feature = "secret")]
362impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
363    GString<V, MIN, MAX, ASCII_ONLY>
364{
365    pub fn zeroize(&mut self) {
366        use zeroize::Zeroize;
367
368        self.buf.zeroize();
369        self.len.zeroize();
370    }
371}