qubit_argument/argument/string_argument.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # String Argument Validation
11//!
12//! Provides validation functionality for string type arguments.
13//!
14
15use super::argument_error::{
16 ArgumentError,
17 ArgumentResult,
18};
19use regex::Regex;
20
21/// String argument validation trait
22///
23/// Provides length, content, and format validation functionality for string types.
24///
25/// # Features
26///
27/// - Length validation support
28/// - Blank checking support
29/// - Regular expression matching support
30/// - Method chaining support
31///
32/// # Use Cases
33///
34/// - User input validation
35/// - Configuration parameter checking
36/// - Text content validation
37///
38///
39/// # Examples
40///
41/// Basic usage (returns `ArgumentResult`):
42///
43/// ```rust
44/// use qubit_argument::argument::{StringArgument, ArgumentResult};
45///
46/// fn set_username(username: &str) -> ArgumentResult<()> {
47/// let username = username
48/// .require_non_blank("username")?
49/// .require_length_in_range("username", 3, 20)?;
50/// println!("Username: {}", username);
51/// Ok(())
52/// }
53/// ```
54///
55/// Converting to other error types:
56///
57/// ```rust
58/// use qubit_argument::argument::StringArgument;
59///
60/// fn set_username(username: &str) -> Result<(), String> {
61/// let username = username
62/// .require_non_blank("username")
63/// .and_then(|u| u.require_length_in_range("username", 3, 20))
64/// .map_err(|e| e.to_string())?;
65/// println!("Username: {}", username);
66/// Ok(())
67/// }
68/// ```
69pub trait StringArgument {
70 /// Validate that string is not blank
71 ///
72 /// Checks if the string is empty or contains only whitespace characters.
73 ///
74 /// # Parameters
75 ///
76 /// * `name` - Parameter name
77 ///
78 /// # Returns
79 ///
80 /// Returns `Ok(self)` if string is not blank, otherwise returns an error
81 ///
82 /// # Examples
83 ///
84 /// ```rust
85 /// use qubit_argument::argument::StringArgument;
86 ///
87 /// let text = "hello";
88 /// assert!(text.require_non_blank("text").is_ok());
89 ///
90 /// let blank = " ";
91 /// assert!(blank.require_non_blank("text").is_err());
92 /// ```
93 fn require_non_blank(&self, name: &str) -> ArgumentResult<&Self>;
94
95 /// Validate that string length equals the specified value
96 ///
97 /// # Parameters
98 ///
99 /// * `name` - Parameter name
100 /// * `length` - Expected length
101 ///
102 /// # Returns
103 ///
104 /// Returns `Ok(self)` if length matches, otherwise returns an error
105 ///
106 /// # Examples
107 ///
108 /// ```rust
109 /// use qubit_argument::argument::StringArgument;
110 ///
111 /// let code = "ABC12";
112 /// assert!(code.require_length_be("code", 5).is_ok());
113 ///
114 /// let wrong_length = "ABC";
115 /// assert!(wrong_length.require_length_be("code", 5).is_err());
116 /// ```
117 fn require_length_be(&self, name: &str, length: usize) -> ArgumentResult<&Self>;
118
119 /// Validate that string length is at least the specified value
120 ///
121 /// # Parameters
122 ///
123 /// * `name` - Parameter name
124 /// * `min_length` - Minimum length
125 ///
126 /// # Returns
127 ///
128 /// Returns `Ok(self)` if length is not less than minimum, otherwise returns an error
129 ///
130 /// # Examples
131 ///
132 /// ```rust
133 /// use qubit_argument::argument::StringArgument;
134 ///
135 /// let password = "secret123";
136 /// assert!(password.require_length_at_least("password", 8).is_ok());
137 /// ```
138 fn require_length_at_least(&self, name: &str, min_length: usize) -> ArgumentResult<&Self>;
139
140 /// Validate that string length is at most the specified value
141 ///
142 /// # Parameters
143 ///
144 /// * `name` - Parameter name
145 /// * `max_length` - Maximum length
146 ///
147 /// # Returns
148 ///
149 /// Returns `Ok(self)` if length is not greater than maximum, otherwise returns an error
150 ///
151 /// # Examples
152 ///
153 /// ```rust
154 /// use qubit_argument::argument::StringArgument;
155 ///
156 /// let description = "Short text";
157 /// assert!(description.require_length_at_most("description", 100).is_ok());
158 /// ```
159 fn require_length_at_most(&self, name: &str, max_length: usize) -> ArgumentResult<&Self>;
160
161 /// Validate that string length is within the specified range
162 ///
163 /// # Parameters
164 ///
165 /// * `name` - Parameter name
166 /// * `min_length` - Minimum length (inclusive)
167 /// * `max_length` - Maximum length (inclusive)
168 ///
169 /// # Returns
170 ///
171 /// Returns `Ok(self)` if length is within range, otherwise returns an error
172 ///
173 /// # Examples
174 ///
175 /// ```rust
176 /// use qubit_argument::argument::StringArgument;
177 ///
178 /// let username = "alice";
179 /// assert!(username.require_length_in_range("username", 3, 20).is_ok());
180 /// ```
181 fn require_length_in_range(
182 &self,
183 name: &str,
184 min_length: usize,
185 max_length: usize,
186 ) -> ArgumentResult<&Self>;
187
188 /// Validate that string matches regular expression
189 ///
190 /// # Parameters
191 ///
192 /// * `name` - Parameter name
193 /// * `pattern` - Regular expression
194 ///
195 /// # Returns
196 ///
197 /// Returns `Ok(self)` if matches, otherwise returns an error
198 ///
199 /// # Examples
200 ///
201 /// ```rust
202 /// use qubit_argument::argument::StringArgument;
203 /// use regex::Regex;
204 ///
205 /// let email = "user@example.com";
206 /// let pattern = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
207 /// assert!(email.require_match("email", &pattern).is_ok());
208 /// ```
209 fn require_match(&self, name: &str, pattern: &Regex) -> ArgumentResult<&Self>;
210
211 /// Validate that string does not match regular expression
212 ///
213 /// # Parameters
214 ///
215 /// * `name` - Parameter name
216 /// * `pattern` - Regular expression
217 ///
218 /// # Returns
219 ///
220 /// Returns `Ok(self)` if does not match, otherwise returns an error
221 ///
222 /// # Examples
223 ///
224 /// ```rust
225 /// use qubit_argument::argument::StringArgument;
226 /// use regex::Regex;
227 ///
228 /// let text = "hello world";
229 /// let pattern = Regex::new(r"\d+").unwrap();
230 /// assert!(text.require_not_match("text", &pattern).is_ok());
231 /// ```
232 fn require_not_match(&self, name: &str, pattern: &Regex) -> ArgumentResult<&Self>;
233}
234
235impl StringArgument for str {
236 #[inline]
237 fn require_non_blank(&self, name: &str) -> ArgumentResult<&Self> {
238 if self.trim().is_empty() {
239 return Err(ArgumentError::new(format!(
240 "Parameter '{}' cannot be empty or contain only whitespace characters",
241 name
242 )));
243 }
244 Ok(self)
245 }
246
247 #[inline]
248 fn require_length_be(&self, name: &str, length: usize) -> ArgumentResult<&Self> {
249 let actual_length = self.len();
250 if actual_length != length {
251 return Err(ArgumentError::new(format!(
252 "Parameter '{}' length must be {} but was {}",
253 name, length, actual_length
254 )));
255 }
256 Ok(self)
257 }
258
259 #[inline]
260 fn require_length_at_least(&self, name: &str, min_length: usize) -> ArgumentResult<&Self> {
261 let actual_length = self.len();
262 if actual_length < min_length {
263 return Err(ArgumentError::new(format!(
264 "Parameter '{}' length must be at least {} but was {}",
265 name, min_length, actual_length
266 )));
267 }
268 Ok(self)
269 }
270
271 #[inline]
272 fn require_length_at_most(&self, name: &str, max_length: usize) -> ArgumentResult<&Self> {
273 let actual_length = self.len();
274 if actual_length > max_length {
275 return Err(ArgumentError::new(format!(
276 "Parameter '{}' length must be at most {} but was {}",
277 name, max_length, actual_length
278 )));
279 }
280 Ok(self)
281 }
282
283 #[inline]
284 fn require_length_in_range(
285 &self,
286 name: &str,
287 min_length: usize,
288 max_length: usize,
289 ) -> ArgumentResult<&Self> {
290 let actual_length = self.len();
291 if actual_length < min_length || actual_length > max_length {
292 return Err(ArgumentError::new(format!(
293 "Parameter '{}' length must be in range [{}, {}] but was {}",
294 name, min_length, max_length, actual_length
295 )));
296 }
297 Ok(self)
298 }
299
300 #[inline]
301 fn require_match(&self, name: &str, pattern: &Regex) -> ArgumentResult<&Self> {
302 if !pattern.is_match(self) {
303 return Err(ArgumentError::new(format!(
304 "Parameter '{}' must match pattern '{}'",
305 name,
306 pattern.as_str()
307 )));
308 }
309 Ok(self)
310 }
311
312 #[inline]
313 fn require_not_match(&self, name: &str, pattern: &Regex) -> ArgumentResult<&Self> {
314 if pattern.is_match(self) {
315 return Err(ArgumentError::new(format!(
316 "Parameter '{}' cannot match pattern '{}'",
317 name,
318 pattern.as_str()
319 )));
320 }
321 Ok(self)
322 }
323}
324
325impl StringArgument for String {
326 #[inline]
327 fn require_non_blank(&self, name: &str) -> ArgumentResult<&Self> {
328 self.as_str().require_non_blank(name).map(|_| self)
329 }
330
331 #[inline]
332 fn require_length_be(&self, name: &str, length: usize) -> ArgumentResult<&Self> {
333 self.as_str().require_length_be(name, length).map(|_| self)
334 }
335
336 #[inline]
337 fn require_length_at_least(&self, name: &str, min_length: usize) -> ArgumentResult<&Self> {
338 self.as_str()
339 .require_length_at_least(name, min_length)
340 .map(|_| self)
341 }
342
343 #[inline]
344 fn require_length_at_most(&self, name: &str, max_length: usize) -> ArgumentResult<&Self> {
345 self.as_str()
346 .require_length_at_most(name, max_length)
347 .map(|_| self)
348 }
349
350 #[inline]
351 fn require_length_in_range(
352 &self,
353 name: &str,
354 min_length: usize,
355 max_length: usize,
356 ) -> ArgumentResult<&Self> {
357 self.as_str()
358 .require_length_in_range(name, min_length, max_length)
359 .map(|_| self)
360 }
361
362 #[inline]
363 fn require_match(&self, name: &str, pattern: &Regex) -> ArgumentResult<&Self> {
364 self.as_str().require_match(name, pattern).map(|_| self)
365 }
366
367 #[inline]
368 fn require_not_match(&self, name: &str, pattern: &Regex) -> ArgumentResult<&Self> {
369 self.as_str().require_not_match(name, pattern).map(|_| self)
370 }
371}