Skip to main content

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}