Skip to main content

qubit_argument/argument/
collection_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//! # Collection Argument Validation
11//!
12//! Provides validation functionality for collection type arguments.
13//!
14
15use super::argument_error::{
16    ArgumentError,
17    ArgumentResult,
18};
19
20/// # Collection Argument Validation Trait
21///
22/// Provides length and content validation functionality for collection types like slices, Vec, arrays, etc.
23///
24/// # Features
25///
26/// - Non-empty checking support
27/// - Length validation support
28/// - Element validation support
29/// - Method chaining support
30///
31/// # Use Cases
32///
33/// - Validating function parameter collections
34/// - Configuration array checking
35/// - User input list validation
36///
37/// # Examples
38///
39/// Basic usage (returns `ArgumentResult`):
40///
41/// ```rust
42/// use qubit_argument::argument::{CollectionArgument, ArgumentResult};
43///
44/// fn process_items(items: &[String]) -> ArgumentResult<()> {
45///     let items = items.require_non_empty("items")?;
46///     println!("Processing {} items", items.len());
47///     Ok(())
48/// }
49/// ```
50///
51/// Converting to other error types:
52///
53/// ```rust
54/// use qubit_argument::argument::CollectionArgument;
55///
56/// fn process_items(items: &[String]) -> Result<(), String> {
57///     let items = items
58///         .require_non_empty("items")
59///         .and_then(|i| i.require_length_in_range("items", 1, 100))
60///         .map_err(|e| e.to_string())?;
61///     println!("Processing {} items", items.len());
62///     Ok(())
63/// }
64/// ```
65///
66///
67pub trait CollectionArgument {
68    /// Validate that the collection is not empty
69    ///
70    /// # Parameters
71    ///
72    /// * `name` - Parameter name
73    ///
74    /// # Returns
75    ///
76    /// Returns `Ok(self)` if the collection is not empty, otherwise returns an error
77    ///
78    /// # Examples
79    ///
80    /// ```rust
81    /// use qubit_argument::argument::CollectionArgument;
82    ///
83    /// let items = vec![1, 2, 3];
84    /// assert!(items.require_non_empty("items").is_ok());
85    ///
86    /// let empty: Vec<i32> = vec![];
87    /// assert!(empty.require_non_empty("items").is_err());
88    /// ```
89    fn require_non_empty(&self, name: &str) -> ArgumentResult<&Self>;
90
91    /// Validate that the collection length equals the specified value
92    ///
93    /// # Parameters
94    ///
95    /// * `name` - Parameter name
96    /// * `length` - Expected length
97    ///
98    /// # Returns
99    ///
100    /// Returns `Ok(self)` if the length matches, otherwise returns an error
101    ///
102    /// # Examples
103    ///
104    /// ```rust
105    /// use qubit_argument::argument::CollectionArgument;
106    ///
107    /// let coordinates = vec![1, 2, 3];
108    /// assert!(coordinates.require_length_be("coordinates", 3).is_ok());
109    /// ```
110    fn require_length_be(&self, name: &str, length: usize) -> ArgumentResult<&Self>;
111
112    /// Validate that the collection length is at least the specified value
113    ///
114    /// # Parameters
115    ///
116    /// * `name` - Parameter name
117    /// * `min_length` - Minimum length
118    ///
119    /// # Returns
120    ///
121    /// Returns `Ok(self)` if the length is not less than the minimum, otherwise returns an error
122    ///
123    /// # Examples
124    ///
125    /// ```rust
126    /// use qubit_argument::argument::CollectionArgument;
127    ///
128    /// let items = vec![1, 2, 3, 4, 5];
129    /// assert!(items.require_length_at_least("items", 3).is_ok());
130    /// ```
131    fn require_length_at_least(&self, name: &str, min_length: usize) -> ArgumentResult<&Self>;
132
133    /// Validate that the collection length is at most the specified value
134    ///
135    /// # Parameters
136    ///
137    /// * `name` - Parameter name
138    /// * `max_length` - Maximum length
139    ///
140    /// # Returns
141    ///
142    /// Returns `Ok(self)` if the length is not greater than the maximum, otherwise returns an error
143    ///
144    /// # Examples
145    ///
146    /// ```rust
147    /// use qubit_argument::argument::CollectionArgument;
148    ///
149    /// let items = vec![1, 2, 3];
150    /// assert!(items.require_length_at_most("items", 10).is_ok());
151    /// ```
152    fn require_length_at_most(&self, name: &str, max_length: usize) -> ArgumentResult<&Self>;
153
154    /// Validate that the collection length is within the specified range
155    ///
156    /// # Parameters
157    ///
158    /// * `name` - Parameter name
159    /// * `min_length` - Minimum length (inclusive)
160    /// * `max_length` - Maximum length (inclusive)
161    ///
162    /// # Returns
163    ///
164    /// Returns `Ok(self)` if the length is within range, otherwise returns an error
165    ///
166    /// # Examples
167    ///
168    /// ```rust
169    /// use qubit_argument::argument::CollectionArgument;
170    ///
171    /// let items = vec![1, 2, 3];
172    /// assert!(items.require_length_in_range("items", 1, 10).is_ok());
173    /// ```
174    fn require_length_in_range(
175        &self,
176        name: &str,
177        min_length: usize,
178        max_length: usize,
179    ) -> ArgumentResult<&Self>;
180}
181
182impl<T> CollectionArgument for [T] {
183    #[inline]
184    fn require_non_empty(&self, name: &str) -> ArgumentResult<&Self> {
185        if self.is_empty() {
186            return Err(ArgumentError::new(format!(
187                "Collection '{}' cannot be empty",
188                name
189            )));
190        }
191        Ok(self)
192    }
193
194    #[inline]
195    fn require_length_be(&self, name: &str, length: usize) -> ArgumentResult<&Self> {
196        let actual_length = self.len();
197        if actual_length != length {
198            return Err(ArgumentError::new(format!(
199                "Collection '{}' length must be {} but was {}",
200                name, length, actual_length
201            )));
202        }
203        Ok(self)
204    }
205
206    #[inline]
207    fn require_length_at_least(&self, name: &str, min_length: usize) -> ArgumentResult<&Self> {
208        let actual_length = self.len();
209        if actual_length < min_length {
210            return Err(ArgumentError::new(format!(
211                "Collection '{}' length must be at least {} but was {}",
212                name, min_length, actual_length
213            )));
214        }
215        Ok(self)
216    }
217
218    #[inline]
219    fn require_length_at_most(&self, name: &str, max_length: usize) -> ArgumentResult<&Self> {
220        let actual_length = self.len();
221        if actual_length > max_length {
222            return Err(ArgumentError::new(format!(
223                "Collection '{}' length must be at most {} but was {}",
224                name, max_length, actual_length
225            )));
226        }
227        Ok(self)
228    }
229
230    #[inline]
231    fn require_length_in_range(
232        &self,
233        name: &str,
234        min_length: usize,
235        max_length: usize,
236    ) -> ArgumentResult<&Self> {
237        let actual_length = self.len();
238        if actual_length < min_length || actual_length > max_length {
239            return Err(ArgumentError::new(format!(
240                "Collection '{}' length must be in range [{}, {}] but was {}",
241                name, min_length, max_length, actual_length
242            )));
243        }
244        Ok(self)
245    }
246}
247
248impl<T> CollectionArgument for Vec<T> {
249    #[inline]
250    fn require_non_empty(&self, name: &str) -> ArgumentResult<&Self> {
251        self.as_slice().require_non_empty(name).map(|_| self)
252    }
253
254    #[inline]
255    fn require_length_be(&self, name: &str, length: usize) -> ArgumentResult<&Self> {
256        self.as_slice()
257            .require_length_be(name, length)
258            .map(|_| self)
259    }
260
261    #[inline]
262    fn require_length_at_least(&self, name: &str, min_length: usize) -> ArgumentResult<&Self> {
263        self.as_slice()
264            .require_length_at_least(name, min_length)
265            .map(|_| self)
266    }
267
268    #[inline]
269    fn require_length_at_most(&self, name: &str, max_length: usize) -> ArgumentResult<&Self> {
270        self.as_slice()
271            .require_length_at_most(name, max_length)
272            .map(|_| self)
273    }
274
275    #[inline]
276    fn require_length_in_range(
277        &self,
278        name: &str,
279        min_length: usize,
280        max_length: usize,
281    ) -> ArgumentResult<&Self> {
282        self.as_slice()
283            .require_length_in_range(name, min_length, max_length)
284            .map(|_| self)
285    }
286}
287
288/// Validate that all elements in the collection are non-null
289///
290/// Checks a collection of Option types to ensure all elements are Some.
291///
292/// # Parameters
293///
294/// * `name` - Parameter name
295/// * `collection` - Collection to validate
296///
297/// # Returns
298///
299/// Returns `Ok(())` if all elements are non-null, otherwise returns an error containing the index of the first null element
300///
301/// # Examples
302///
303/// ```rust
304/// use qubit_argument::argument::require_element_non_null;
305///
306/// let items = vec![Some(1), Some(2), Some(3)];
307/// assert!(require_element_non_null("items", &items).is_ok());
308///
309/// let items_with_none = vec![Some(1), None, Some(3)];
310/// assert!(require_element_non_null("items", &items_with_none).is_err());
311/// ```
312///
313///
314#[inline]
315pub fn require_element_non_null<T>(name: &str, collection: &[Option<T>]) -> ArgumentResult<()> {
316    for (index, item) in collection.iter().enumerate() {
317        if item.is_none() {
318            return Err(ArgumentError::new(format!(
319                "Collection '{}': element at index {} cannot be null",
320                name, index
321            )));
322        }
323    }
324    Ok(())
325}