cjtoolkit_structured_validator/common/
string_validator.rs

1//! This module contains structures and traits for working with strings.
2
3use unicode_segmentation::UnicodeSegmentation;
4
5/// A structure for validating strings with specific constraints.
6pub struct StringValidator<'a>(&'a str, usize);
7
8impl<'a> StringValidator<'a> {
9    /// A constant array that contains a predefined set of 30 special characters.
10    ///
11    /// These special characters are commonly used in programming, input validation,
12    /// and applications where character-based operations are required, such as parsing,
13    /// formatting, or password generation.
14    ///
15    /// # Characters included:
16    /// - `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `-`, `_`, `=`, `+`
17    /// - `[`, `]`, `{`, `}`, `\`, `|`, `;`, `:`, `'`, `"`
18    /// - `,`, `.`, `<`, `>`, `/`, `?`
19    ///
20    /// # Notes
21    /// This constant is immutable and should be used for read-only.
22    pub const SPECIAL_CHARS: [char; 30] = [
23        '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', ']', '{', '}',
24        '\\', '|', ';', ':', '\'', '"', ',', '.', '<', '>', '/', '?',
25    ];
26
27    fn new(s: &'a str) -> Self {
28        Self(s, s.graphemes(true).count())
29    }
30
31    /// Returns the number of graphemes in the structure.
32    ///
33    /// This function retrieves the count of graphemes stored within the current instance.
34    /// It assumes that the second element of the tuple (`self.1`) represents the grapheme count.
35    ///
36    /// # Returns
37    ///
38    /// * `usize` - The total number of graphemes.
39    ///
40    pub fn count_graphemes(&self) -> usize {
41        self.1
42    }
43
44    /// Checks whether the current object is empty.
45    ///
46    /// # Returns
47    /// - `true` if the internal value (`self.1`) is equal to 0, indicating that the object is empty.
48    /// - `false` otherwise.
49    ///
50    pub fn is_empty(&self) -> bool {
51        self.1 == 0
52    }
53
54    /// Checks if the string contains any special character from a predefined set.
55    ///
56    /// # Returns
57    /// * `true` - If the string contains at least one special character.
58    /// * `false` - If the string does not contain any special characters.
59    ///
60    /// # Implementation Details
61    /// - Checks each character in the string to determine if it is part of the `SPECIAL_CHARS` set.
62    ///
63    pub fn has_special_chars(&self) -> bool {
64        self.0.chars().any(|c| Self::SPECIAL_CHARS.contains(&c))
65    }
66
67    /// Counts the number of special characters in the string.
68    ///
69    /// This function iterates through the characters of the string and determines
70    /// how many of them are considered special. A special character is defined as
71    /// any character present in the `SPECIAL_CHARS` set.
72    ///
73    /// # Returns
74    /// * `usize` - The number of special characters in the string.
75    ///
76    /// # Note
77    /// * The `SPECIAL_CHARS` set is a pre-defined constant within the implementation
78    ///   of this type, specifying what qualifies as a special character.
79    pub fn count_special_chars(&self) -> usize {
80        self.0
81            .chars()
82            .filter(|c| Self::SPECIAL_CHARS.contains(c))
83            .count()
84    }
85
86    /// Checks if the string contains at least one ASCII uppercase character.
87    ///
88    /// # Returns
89    ///
90    /// * `true` - If the string contains at least one ASCII uppercase character.
91    /// * `false` - If the string does not contain any ASCII uppercase characters.
92    ///
93    /// # Note
94    ///
95    /// This function iterates over the characters of the string
96    /// and checks if any character is an ASCII uppercase letter
97    /// (i.e., 'A' to 'Z').
98    pub fn has_ascii_uppercase(&self) -> bool {
99        self.0.chars().any(|c| c.is_ascii_uppercase())
100    }
101
102    /// Counts and returns the number of ASCII uppercase letters in the string.
103    ///
104    /// # Description
105    /// This function iterates over the characters of the string, filters out
106    /// those that are ASCII uppercase letters, and returns the total count.
107    ///
108    /// # Returns
109    /// * `usize` - The number of ASCII uppercase letters in the string.
110    ///
111    pub fn count_ascii_uppercase(&self) -> usize {
112        self.0.chars().filter(|c| c.is_ascii_uppercase()).count()
113    }
114
115    /// Checks if the string slice contains any ASCII lowercase characters.
116    ///
117    /// # Returns
118    /// - `true` if the string contains at least one ASCII lowercase character (`'a-zA-Z'`).
119    /// - `false` if no ASCII lowercase characters are found.
120    ///
121    /// This method iterates through all the characters of the inner string (`self.0`)
122    /// and checks if any character satisfies the `is_ascii_lowercase` predicate.
123    /// It returns as soon as a lowercase ASCII character is found.
124    pub fn has_ascii_lowercase(&self) -> bool {
125        self.0.chars().any(|c| c.is_ascii_lowercase())
126    }
127
128    /// Counts the number of ASCII lowercase characters in the string.
129    ///
130    /// This function iterates through each character of the string stored in the first field of the
131    /// tuple (`self.0`), filters out only those that are lowercase ASCII alphabetic characters (a-z),
132    /// and returns their count.
133    ///
134    /// # Returns
135    ///
136    /// * `usize` - The number of ASCII lowercase characters in the string.
137    ///
138    /// Note: This function only checks for ASCII lowercase characters. Non-ASCII characters are ignored.
139    pub fn count_ascii_lowercase(&self) -> usize {
140        self.0.chars().filter(|c| c.is_ascii_lowercase()).count()
141    }
142
143    /// Checks if the current object contains both ASCII uppercase and lowercase characters.
144    ///
145    /// This method evaluates whether the instance has at least one ASCII uppercase letter
146    /// (e.g., 'A'-'Z') and at least one ASCII lowercase letter (e.g., 'a'-'z') simultaneously.
147    ///
148    /// # Returns
149    /// * `true` - If the instance contains both uppercase and lowercase ASCII characters.
150    /// * `false` - If the instance does not contain both uppercase and lowercase ASCII characters.
151    ///
152    pub fn has_ascii_uppercase_and_lowercase(&self) -> bool {
153        self.has_ascii_uppercase() && self.has_ascii_lowercase()
154    }
155
156    /// Counts the total number of ASCII uppercase and lowercase characters in the current instance.
157    ///
158    /// This method combines the results of `count_ascii_uppercase` and `count_ascii_lowercase`,
159    /// returning their sum. It provides a convenient way to calculate the total count of ASCII alphabetic
160    /// characters (both uppercase and lowercase).
161    ///
162    /// # Returns
163    ///
164    /// * `usize` - The sum of the ASCII uppercase and lowercase character counts.
165    ///
166    /// Note: The functionality of this method depends on the implementation of both
167    /// `count_ascii_uppercase` and `count_ascii_lowercase` methods, which must be defined
168    /// elsewhere in the same context.
169    pub fn count_ascii_uppercase_and_lowercase(&self) -> usize {
170        self.count_ascii_uppercase() + self.count_ascii_lowercase()
171    }
172
173    /// Checks whether the inner string contains at least one ASCII digit.
174    ///
175    /// # Returns
176    /// - `true` if the string contains at least one ASCII digit (`0-9`).
177    /// - `false` otherwise.
178    ///
179    /// This method works by iterating over the characters of the inner string
180    /// and checking if any character is an ASCII digit.
181    pub fn has_ascii_digit(&self) -> bool {
182        self.0.chars().any(|c| c.is_ascii_digit())
183    }
184
185    /// Counts the number of ASCII digit characters in the string.
186    ///
187    /// This function iterates through the characters of the string, filters out
188    /// the characters that are ASCII digits (`0-9`), and returns the count of those digits.
189    ///
190    /// # Returns
191    ///
192    /// * `usize` - The total number of ASCII digit characters in the string.
193    ///
194    /// Note: Non-ASCII digits (e.g., Arabic numerals) will not be counted.
195    pub fn count_ascii_digit(&self) -> usize {
196        self.0.chars().filter(|c| c.is_ascii_digit()).count()
197    }
198
199    /// Checks if the string contains any ASCII alphanumeric characters.
200    ///
201    /// This method checks if at least one character in the string is an ASCII alphanumeric character.
202    /// ASCII alphanumeric characters are defined as 'a' to 'z', 'A' to 'Z', and '0' to '9'.
203    ///
204    /// # Returns
205    ///
206    /// * `true` - If the string contains at least one ASCII alphanumeric character.
207    /// * `false` - If the string does not contain any ASCII alphanumeric characters.
208    ///
209    pub fn has_ascii_alphanumeric(&self) -> bool {
210        self.0.chars().any(|c| c.is_ascii_alphanumeric())
211    }
212
213    /// Counts the number of ASCII alphanumeric characters in the string.
214    ///
215    /// This function iterates through the characters of the string, filters out
216    /// those that are ASCII alphanumeric (letters and digits), and returns the count
217    /// of such characters.
218    ///
219    /// # Returns
220    /// * `usize` - The number of ASCII alphanumeric characters in the string.
221    ///
222    /// Note: This function will only count characters that are both ASCII and alphanumeric.
223    /// Non-ASCII characters or symbols will not be included in the count.
224    pub fn count_ascii_alphanumeric(&self) -> usize {
225        self.0.chars().filter(|c| c.is_ascii_alphanumeric()).count()
226    }
227}
228
229trait StrSealed {}
230
231/// A trait providing an extension for string validation functionality. This trait is sealed and
232/// cannot be implemented outside the module where it is defined. It is designed to extend
233/// the functionality of types implementing the private `StrSealed` trait.
234///
235/// # Required Methods
236/// - `as_string_validator`: Converts the type implementing this trait into a
237///   `StringValidator`, enabling validation operations on the string.
238///
239/// # Note
240/// The `#[allow(private_bounds)]` attribute is used as this trait relies on the
241/// private `StrSealed` trait as a bound to enforce its sealing.
242///
243/// # Associated Types
244/// - This trait does not introduce any associated types.
245///
246/// # Methods
247///
248/// - `as_string_validator(&'_ self) -> StringValidator<'_>`
249///   - Returns a `StringValidator` which allows performing validation operations
250///     on the implementing type.
251///   - This method borrows self for a lifetime (`'_`) and produces a
252///     `StringValidator` tied to the lifetime of the reference.
253///
254#[allow(private_bounds)]
255pub trait StrValidationExtension: StrSealed {
256    fn as_string_validator(&'_ self) -> StringValidator<'_>;
257}
258
259impl StrSealed for &str {}
260
261impl StrValidationExtension for &str {
262    fn as_string_validator(&'_ self) -> StringValidator<'_> {
263        StringValidator::new(self)
264    }
265}
266
267impl StrSealed for String {}
268
269impl StrValidationExtension for String {
270    fn as_string_validator(&'_ self) -> StringValidator<'_> {
271        StringValidator::new(self)
272    }
273}