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}