Skip to main content

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}