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