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}