qubit_argument/argument/collection_argument.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Ownership-preserving validation for collection arguments.
9
10use crate::argument::{
11 ArgumentError,
12 ArgumentErrorKind,
13 ArgumentResult,
14 LengthConstraint,
15 LengthMetric,
16 sealed::Sealed,
17};
18
19/// Validates collection lengths while preserving the original collection.
20///
21/// Every successful method consumes and returns the same collection value
22/// without cloning its elements. Implementations are provided for owned
23/// vectors, borrowed slices, and arrays. Length failures carry
24/// [`LengthMetric::Elements`].
25///
26/// The trait is sealed to those library-supported collection forms.
27pub trait CollectionArgument: Sealed + Sized {
28 /// Requires this collection to contain at least one element.
29 ///
30 /// Success returns the original collection without cloning its elements.
31 /// An empty collection returns [`ArgumentErrorKind::Empty`] at `path`.
32 fn require_non_empty(self, path: &str) -> ArgumentResult<Self>;
33
34 /// Requires this collection to contain exactly `expected` elements.
35 ///
36 /// Success returns the original collection without cloning its elements.
37 /// A different length returns [`ArgumentErrorKind::Length`] at `path` with
38 /// an exact constraint.
39 fn require_len(self, path: &str, expected: usize) -> ArgumentResult<Self>;
40
41 /// Requires this collection to contain at least `min` elements.
42 ///
43 /// Success returns the original collection without cloning its elements.
44 /// A shorter collection returns [`ArgumentErrorKind::Length`] at `path`
45 /// with a minimum constraint.
46 fn require_len_at_least(
47 self,
48 path: &str,
49 min: usize,
50 ) -> ArgumentResult<Self>;
51
52 /// Requires this collection to contain at most `max` elements.
53 ///
54 /// Success returns the original collection without cloning its elements.
55 /// A longer collection returns [`ArgumentErrorKind::Length`] at `path`
56 /// with a maximum constraint.
57 fn require_len_at_most(
58 self,
59 path: &str,
60 max: usize,
61 ) -> ArgumentResult<Self>;
62
63 /// Requires this collection's length to lie in `min..=max`.
64 ///
65 /// The range is validated before the observed length. If `min > max`, this
66 /// returns [`ArgumentErrorKind::InvalidLengthConstraint`] at `path`;
67 /// otherwise, an out-of-range length returns
68 /// [`ArgumentErrorKind::Length`]. Success returns the original collection
69 /// without cloning its elements.
70 fn require_len_in(
71 self,
72 path: &str,
73 min: usize,
74 max: usize,
75 ) -> ArgumentResult<Self>;
76}
77
78impl<T> CollectionArgument for Vec<T> {
79 /// Validates non-emptiness and returns the original vector.
80 ///
81 /// An empty vector returns [`ArgumentErrorKind::Empty`] at `path`.
82 #[inline]
83 fn require_non_empty(self, path: &str) -> ArgumentResult<Self> {
84 if self.is_empty() {
85 return Err(ArgumentError::new(path, ArgumentErrorKind::Empty));
86 }
87 Ok(self)
88 }
89
90 /// Validates the exact length and returns the original vector.
91 ///
92 /// A mismatch returns [`ArgumentErrorKind::Length`] at `path`.
93 #[inline]
94 fn require_len(self, path: &str, expected: usize) -> ArgumentResult<Self> {
95 validate_length(path, self.len(), LengthConstraint::Exact(expected))?;
96 Ok(self)
97 }
98
99 /// Validates the minimum length and returns the original vector.
100 ///
101 /// A length below `min` returns [`ArgumentErrorKind::Length`] at `path`.
102 #[inline]
103 fn require_len_at_least(
104 self,
105 path: &str,
106 min: usize,
107 ) -> ArgumentResult<Self> {
108 validate_length(path, self.len(), LengthConstraint::AtLeast(min))?;
109 Ok(self)
110 }
111
112 /// Validates the maximum length and returns the original vector.
113 ///
114 /// A length above `max` returns [`ArgumentErrorKind::Length`] at `path`.
115 #[inline]
116 fn require_len_at_most(
117 self,
118 path: &str,
119 max: usize,
120 ) -> ArgumentResult<Self> {
121 validate_length(path, self.len(), LengthConstraint::AtMost(max))?;
122 Ok(self)
123 }
124
125 /// Validates an inclusive length range and returns the original vector.
126 ///
127 /// A reversed range returns
128 /// [`ArgumentErrorKind::InvalidLengthConstraint`] at `path`; an
129 /// out-of-range length returns [`ArgumentErrorKind::Length`].
130 #[inline]
131 fn require_len_in(
132 self,
133 path: &str,
134 min: usize,
135 max: usize,
136 ) -> ArgumentResult<Self> {
137 validate_length(
138 path,
139 self.len(),
140 LengthConstraint::InRange { min, max },
141 )?;
142 Ok(self)
143 }
144}
145
146impl<T> CollectionArgument for &[T] {
147 /// Validates non-emptiness and returns the original borrowed slice.
148 ///
149 /// An empty slice returns [`ArgumentErrorKind::Empty`] at `path`.
150 #[inline]
151 fn require_non_empty(self, path: &str) -> ArgumentResult<Self> {
152 if self.is_empty() {
153 return Err(ArgumentError::new(path, ArgumentErrorKind::Empty));
154 }
155 Ok(self)
156 }
157
158 /// Validates the exact length and returns the original borrowed slice.
159 ///
160 /// A mismatch returns [`ArgumentErrorKind::Length`] at `path`.
161 #[inline]
162 fn require_len(self, path: &str, expected: usize) -> ArgumentResult<Self> {
163 validate_length(path, self.len(), LengthConstraint::Exact(expected))?;
164 Ok(self)
165 }
166
167 /// Validates the minimum length and returns the original borrowed slice.
168 ///
169 /// A length below `min` returns [`ArgumentErrorKind::Length`] at `path`.
170 #[inline]
171 fn require_len_at_least(
172 self,
173 path: &str,
174 min: usize,
175 ) -> ArgumentResult<Self> {
176 validate_length(path, self.len(), LengthConstraint::AtLeast(min))?;
177 Ok(self)
178 }
179
180 /// Validates the maximum length and returns the original borrowed slice.
181 ///
182 /// A length above `max` returns [`ArgumentErrorKind::Length`] at `path`.
183 #[inline]
184 fn require_len_at_most(
185 self,
186 path: &str,
187 max: usize,
188 ) -> ArgumentResult<Self> {
189 validate_length(path, self.len(), LengthConstraint::AtMost(max))?;
190 Ok(self)
191 }
192
193 /// Validates an inclusive length range and returns the borrowed slice.
194 ///
195 /// A reversed range returns
196 /// [`ArgumentErrorKind::InvalidLengthConstraint`] at `path`; an
197 /// out-of-range length returns [`ArgumentErrorKind::Length`].
198 #[inline]
199 fn require_len_in(
200 self,
201 path: &str,
202 min: usize,
203 max: usize,
204 ) -> ArgumentResult<Self> {
205 validate_length(
206 path,
207 self.len(),
208 LengthConstraint::InRange { min, max },
209 )?;
210 Ok(self)
211 }
212}
213
214impl<T, const N: usize> CollectionArgument for [T; N] {
215 /// Validates non-emptiness and returns the original array.
216 ///
217 /// A zero-length array returns [`ArgumentErrorKind::Empty`] at `path`.
218 #[inline]
219 fn require_non_empty(self, path: &str) -> ArgumentResult<Self> {
220 if N == 0 {
221 return Err(ArgumentError::new(path, ArgumentErrorKind::Empty));
222 }
223 Ok(self)
224 }
225
226 /// Validates the exact length and returns the original array.
227 ///
228 /// A mismatch returns [`ArgumentErrorKind::Length`] at `path`.
229 #[inline]
230 fn require_len(self, path: &str, expected: usize) -> ArgumentResult<Self> {
231 validate_length(path, N, LengthConstraint::Exact(expected))?;
232 Ok(self)
233 }
234
235 /// Validates the minimum length and returns the original array.
236 ///
237 /// A length below `min` returns [`ArgumentErrorKind::Length`] at `path`.
238 #[inline]
239 fn require_len_at_least(
240 self,
241 path: &str,
242 min: usize,
243 ) -> ArgumentResult<Self> {
244 validate_length(path, N, LengthConstraint::AtLeast(min))?;
245 Ok(self)
246 }
247
248 /// Validates the maximum length and returns the original array.
249 ///
250 /// A length above `max` returns [`ArgumentErrorKind::Length`] at `path`.
251 #[inline]
252 fn require_len_at_most(
253 self,
254 path: &str,
255 max: usize,
256 ) -> ArgumentResult<Self> {
257 validate_length(path, N, LengthConstraint::AtMost(max))?;
258 Ok(self)
259 }
260
261 /// Validates an inclusive length range and returns the original array.
262 ///
263 /// A reversed range returns
264 /// [`ArgumentErrorKind::InvalidLengthConstraint`] at `path`; an
265 /// out-of-range length returns [`ArgumentErrorKind::Length`].
266 #[inline]
267 fn require_len_in(
268 self,
269 path: &str,
270 min: usize,
271 max: usize,
272 ) -> ArgumentResult<Self> {
273 validate_length(path, N, LengthConstraint::InRange { min, max })?;
274 Ok(self)
275 }
276}
277
278/// Validates an observed collection length against a structured constraint.
279///
280/// `path` identifies any failure, `actual` is the observed element count, and
281/// `constraint` describes the required relationship. A reversed inclusive
282/// range returns [`ArgumentErrorKind::InvalidLengthConstraint`] before
283/// `actual` is checked. Any other unsatisfied constraint returns
284/// [`ArgumentErrorKind::Length`]; a satisfied constraint returns `Ok(())`.
285fn validate_length(
286 path: &str,
287 actual: usize,
288 constraint: LengthConstraint,
289) -> ArgumentResult<()> {
290 if let LengthConstraint::InRange { min, max } = &constraint
291 && min > max
292 {
293 return Err(ArgumentError::new(
294 path,
295 ArgumentErrorKind::InvalidLengthConstraint {
296 constraint,
297 metric: LengthMetric::Elements,
298 },
299 ));
300 }
301
302 let is_valid = match &constraint {
303 LengthConstraint::Exact(expected) => actual == *expected,
304 LengthConstraint::AtLeast(min) => actual >= *min,
305 LengthConstraint::AtMost(max) => actual <= *max,
306 LengthConstraint::InRange { min, max } => {
307 actual >= *min && actual <= *max
308 }
309 };
310 if is_valid {
311 Ok(())
312 } else {
313 Err(ArgumentError::new(
314 path,
315 ArgumentErrorKind::Length {
316 actual,
317 constraint,
318 metric: LengthMetric::Elements,
319 },
320 ))
321 }
322}