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