Skip to main content

qubit_validator/binding/
argument_reader.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
9//! Strict, one-pass decoding of named validator parameters.
10
11use std::fmt;
12
13use super::BindError;
14use super::BindErrorKind;
15use crate::NamedValidationArgument;
16use crate::ValidationArgument;
17
18/// Reads declared arguments exactly once while rejecting duplicates, unknown
19/// names, and lossy numeric conversions.
20///
21/// A present parameter can be read at most once. A typed read consumes the
22/// parameter before decoding it, so type and range errors also complete that
23/// parameter's consumption. Call [`Self::finish`] to reject parameters that
24/// were never read.
25///
26/// # Type Parameters
27///
28/// - `'a`: Lifetime shared by the declared parameter names and borrowed values.
29///
30/// # Examples
31///
32/// ```
33/// use qubit_validator::ArgumentReader;
34/// use qubit_validator::NamedValidationArgument;
35/// use qubit_validator::ValidationArgument;
36///
37/// let arguments = [NamedValidationArgument::new(
38///     "limit",
39///     ValidationArgument::Unsigned(10),
40/// )];
41/// let mut reader = ArgumentReader::new(&arguments)?;
42/// assert_eq!(reader.required_u32("limit")?, 10);
43/// reader.finish()?;
44/// # Ok::<(), qubit_validator::BindError>(())
45/// ```
46pub struct ArgumentReader<'a> {
47    /// Declared parameters in caller-provided order.
48    args: &'a [NamedValidationArgument<'a>],
49    /// Per-parameter flags recording successful or failed read attempts.
50    consumed: Vec<bool>,
51}
52
53impl<'a> ArgumentReader<'a> {
54    /// Creates a reader and checks for duplicate parameter names.
55    ///
56    /// # Parameters
57    ///
58    /// - `args`: Declared parameters to read, in caller-provided order.
59    ///
60    /// # Returns
61    ///
62    /// A reader that tracks consumption of each declared parameter.
63    ///
64    /// # Errors
65    ///
66    /// Returns `DuplicateParameter` when a name occurs more than once.
67    pub fn new(args: &'a [NamedValidationArgument<'a>]) -> Result<Self, BindError> {
68        for (index, argument) in args.iter().enumerate() {
69            if args[..index].iter().any(|previous| previous.name() == argument.name()) {
70                return Err(BindError::new(BindErrorKind::DuplicateParameter).with_parameter(argument.name()));
71            }
72        }
73        Ok(Self {
74            args,
75            consumed: vec![false; args.len()],
76        })
77    }
78
79    /// Reads a required argument once without decoding its value.
80    ///
81    /// The caller is responsible for checking the returned variant's type and
82    /// any domain-specific constraints. A successful read consumes the
83    /// argument.
84    ///
85    /// # Parameters
86    ///
87    /// - `name`: Name of the required parameter.
88    ///
89    /// # Returns
90    ///
91    /// The parameter's value without decoding its variant.
92    ///
93    /// # Errors
94    ///
95    /// Returns `MissingParameter` when the name is absent, or
96    /// `ParameterAlreadyConsumed` when the argument was previously read.
97    #[inline]
98    pub fn required_argument(&mut self, name: &str) -> Result<ValidationArgument<'a>, BindError> {
99        self.take(name)
100    }
101
102    /// Reads a required unsigned 32-bit integer once.
103    ///
104    /// # Parameters
105    ///
106    /// - `name`: Name of the required integer parameter.
107    ///
108    /// # Returns
109    ///
110    /// The decoded `u32` value.
111    ///
112    /// # Errors
113    ///
114    /// Returns `MissingParameter` when the parameter is absent,
115    /// `ParameterAlreadyConsumed` when it was previously read,
116    /// `ParameterTypeMismatch` when it is not an integer, or
117    /// `ParameterOutOfRange` when its value cannot be represented as a `u32`.
118    #[inline]
119    pub fn required_u32(&mut self, name: &str) -> Result<u32, BindError> {
120        self.u32_value(name, true).map(|value| value.expect("required value"))
121    }
122
123    /// Reads an optional unsigned 32-bit integer once.
124    ///
125    /// # Parameters
126    ///
127    /// - `name`: Name of the optional integer parameter.
128    ///
129    /// # Returns
130    ///
131    /// Returns `Some` with the decoded value when the parameter is present,
132    /// or `None` when it is absent.
133    ///
134    /// # Errors
135    ///
136    /// Returns `ParameterAlreadyConsumed` when the present parameter was
137    /// previously read, `ParameterTypeMismatch` when it is not an integer, or
138    /// `ParameterOutOfRange` when its value cannot be represented as a `u32`.
139    #[inline]
140    pub fn optional_u32(&mut self, name: &str) -> Result<Option<u32>, BindError> {
141        self.u32_value(name, false)
142    }
143
144    /// Reads an optional platform-sized unsigned integer once.
145    ///
146    /// The representable range depends on the target architecture, commonly
147    /// 32 or 64 bits.
148    ///
149    /// # Parameters
150    ///
151    /// - `name`: Name of the optional integer parameter.
152    ///
153    /// # Returns
154    ///
155    /// Returns `Some` with the decoded value when the parameter is present,
156    /// or `None` when it is absent.
157    ///
158    /// # Errors
159    ///
160    /// Returns `ParameterAlreadyConsumed` when the present parameter was
161    /// previously read, `ParameterTypeMismatch` when it is not an integer, or
162    /// `ParameterOutOfRange` when it is negative or cannot be represented as
163    /// a `usize`. Each error includes the parameter name.
164    pub fn optional_usize(&mut self, name: &str) -> Result<Option<usize>, BindError> {
165        let Some(value) = self.take_optional(name)? else {
166            return Ok(None);
167        };
168        let converted = match value {
169            ValidationArgument::Unsigned(value) => usize::try_from(value),
170            ValidationArgument::Integer(value) if value >= 0 => usize::try_from(value as u128),
171            ValidationArgument::Integer(_) => return Err(Self::range_error(name)),
172            _ => return Err(Self::type_error(name)),
173        };
174        converted.map(Some).map_err(|_| Self::range_error(name))
175    }
176
177    /// Reads a required string argument once.
178    ///
179    /// # Parameters
180    ///
181    /// - `name`: Name of the required string parameter.
182    ///
183    /// # Returns
184    ///
185    /// The borrowed string value.
186    ///
187    /// # Errors
188    ///
189    /// Returns `MissingParameter` when the parameter is absent,
190    /// `ParameterAlreadyConsumed` when it was previously read, or
191    /// `ParameterTypeMismatch` when it is not a string.
192    pub fn required_str(&mut self, name: &str) -> Result<&'a str, BindError> {
193        match self.take(name)? {
194            ValidationArgument::String(value) => Ok(value),
195            _ => Err(Self::type_error(name)),
196        }
197    }
198
199    /// Reads an optional string argument once without allocating.
200    ///
201    /// The returned string borrows the value supplied when this reader was
202    /// created. A missing parameter is not consumed and can be queried again.
203    /// A present parameter is consumed before its type is checked, including
204    /// when the value has the wrong type.
205    ///
206    /// # Parameters
207    ///
208    /// - `name`: Name of the optional string parameter.
209    ///
210    /// # Returns
211    ///
212    /// Returns `Some` with the original borrowed string when the parameter is
213    /// present, or `None` when it is absent.
214    ///
215    /// # Errors
216    ///
217    /// Returns `ParameterAlreadyConsumed` when the present parameter was
218    /// previously read, or `ParameterTypeMismatch` when its value is not a
219    /// string. Both errors include the parameter name. A failed read consumes
220    /// the present parameter; callers must propagate the error instead of
221    /// continuing with other reads.
222    pub fn optional_str(&mut self, name: &str) -> Result<Option<&'a str>, BindError> {
223        let Some(value) = self.take_optional(name)? else {
224            return Ok(None);
225        };
226        match value {
227            ValidationArgument::String(value) => Ok(Some(value)),
228            _ => Err(Self::type_error(name)),
229        }
230    }
231
232    /// Reads an optional boolean argument once.
233    ///
234    /// # Parameters
235    ///
236    /// - `name`: Name of the optional boolean parameter.
237    ///
238    /// # Returns
239    ///
240    /// Returns `Some` with the boolean when the parameter is present, or
241    /// `None` when it is absent.
242    ///
243    /// # Errors
244    ///
245    /// Returns `ParameterAlreadyConsumed` when the present parameter was
246    /// previously read or `ParameterTypeMismatch` when it is not a boolean.
247    pub fn optional_bool(&mut self, name: &str) -> Result<Option<bool>, BindError> {
248        let Some(value) = self.take_optional(name)? else {
249            return Ok(None);
250        };
251        match value {
252            ValidationArgument::Bool(value) => Ok(Some(value)),
253            _ => Err(Self::type_error(name)),
254        }
255    }
256
257    /// Rejects every argument which was not consumed by a typed reader.
258    ///
259    /// # Returns
260    ///
261    /// `Ok(())` when every declared argument was consumed.
262    ///
263    /// # Errors
264    ///
265    /// Returns `UnknownParameter` for the first argument that was never read.
266    pub fn finish(&self) -> Result<(), BindError> {
267        if let Some((_index, argument)) = self.args.iter().enumerate().find(|(index, _)| !self.consumed[*index]) {
268            return Err(BindError::new(BindErrorKind::UnknownParameter).with_parameter(argument.name()));
269        }
270        Ok(())
271    }
272
273    /// Reads and converts an optional or required unsigned 32-bit parameter.
274    ///
275    /// # Parameters
276    ///
277    /// - `name`: Name of the parameter to read.
278    /// - `required`: Whether absence should produce `MissingParameter`.
279    fn u32_value(&mut self, name: &str, required: bool) -> Result<Option<u32>, BindError> {
280        let Some(value) = self.take_optional(name)? else {
281            if required {
282                return Err(BindError::new(BindErrorKind::MissingParameter).with_parameter(name));
283            }
284            return Ok(None);
285        };
286        let value = match value {
287            ValidationArgument::Unsigned(value) => u32::try_from(value),
288            ValidationArgument::Integer(value) if value >= 0 => u32::try_from(value as u128),
289            ValidationArgument::Integer(_) => {
290                return Err(Self::range_error(name));
291            }
292            _ => return Err(Self::type_error(name)),
293        }
294        .map_err(|_| Self::range_error(name))?;
295        Ok(Some(value))
296    }
297
298    /// Takes a present parameter once, or returns `None` when it is absent.
299    ///
300    /// # Parameters
301    ///
302    /// - `name`: Name of the parameter to find and consume.
303    ///
304    /// # Returns
305    ///
306    /// `Some` with the parameter value when present, or `None` when absent.
307    ///
308    /// # Errors
309    ///
310    /// Returns `ParameterAlreadyConsumed` when the parameter was already read.
311    fn take_optional(&mut self, name: &str) -> Result<Option<ValidationArgument<'a>>, BindError> {
312        let Some(index) = self.args.iter().position(|argument| argument.name() == name) else {
313            return Ok(None);
314        };
315        if self.consumed[index] {
316            return Err(BindError::new(BindErrorKind::ParameterAlreadyConsumed).with_parameter(name));
317        }
318        self.consumed[index] = true;
319        Ok(Some(self.args[index].value()))
320    }
321
322    /// Takes a required parameter once.
323    ///
324    /// # Parameters
325    ///
326    /// - `name`: Name of the required parameter.
327    ///
328    /// # Returns
329    ///
330    /// The parameter value.
331    ///
332    /// # Errors
333    ///
334    /// Returns `MissingParameter` when absent or `ParameterAlreadyConsumed`
335    /// when previously read.
336    fn take(&mut self, name: &str) -> Result<ValidationArgument<'a>, BindError> {
337        self.take_optional(name)?
338            .ok_or_else(|| BindError::new(BindErrorKind::MissingParameter).with_parameter(name))
339    }
340
341    /// Builds a type-mismatch error without retaining the rejected value.
342    ///
343    /// # Parameters
344    ///
345    /// - `name`: Name to attach to the error.
346    ///
347    /// # Returns
348    ///
349    /// A `ParameterTypeMismatch` error for `name`.
350    #[inline]
351    fn type_error(name: &str) -> BindError {
352        BindError::new(BindErrorKind::ParameterTypeMismatch).with_parameter(name)
353    }
354
355    /// Builds a range error without retaining the rejected value.
356    ///
357    /// # Parameters
358    ///
359    /// - `name`: Name to attach to the error.
360    ///
361    /// # Returns
362    ///
363    /// A `ParameterOutOfRange` error for `name`.
364    #[inline]
365    fn range_error(name: &str) -> BindError {
366        BindError::new(BindErrorKind::ParameterOutOfRange).with_parameter(name)
367    }
368}
369
370impl fmt::Debug for ArgumentReader<'_> {
371    /// Formats only the parameter count so argument names and values stay
372    /// private.
373    ///
374    /// # Parameters
375    ///
376    /// - `formatter`: Formatter that receives the redacted debug
377    ///   representation.
378    ///
379    /// # Returns
380    ///
381    /// The formatter's result.
382    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
383        formatter
384            .debug_struct("ArgumentReader")
385            .field("argument_count", &self.args.len())
386            .finish()
387    }
388}