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}