Skip to main content

qubit_argument/argument/
numeric_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//! # Numeric Argument Validation
11//!
12//! Provides validation functionality for numeric type arguments.
13//!
14
15use super::argument_error::{
16    ArgumentError,
17    ArgumentResult,
18};
19use std::fmt::Display;
20
21/// Internal trait that restricts `NumericArgument` to actual numeric types.
22trait NumericValue: PartialOrd + Display + Copy {
23    /// Returns the additive identity (zero) of the numeric type.
24    fn zero() -> Self;
25
26    /// Rejects NaN values when the numeric type supports NaN.
27    ///
28    /// Non-floating-point types always pass this check.
29    #[inline]
30    fn reject_nan(self, _name: &str) -> ArgumentResult<()> {
31        Ok(())
32    }
33}
34
35macro_rules! impl_numeric_value_for_int {
36    ($($t:ty),+ $(,)?) => {
37        $(
38            impl NumericValue for $t {
39                #[inline]
40                fn zero() -> Self {
41                    0
42                }
43            }
44        )+
45    };
46}
47
48impl_numeric_value_for_int!(i8, i16, i32, i64, i128, isize);
49impl_numeric_value_for_int!(u8, u16, u32, u64, u128, usize);
50
51impl NumericValue for f32 {
52    #[inline]
53    fn zero() -> Self {
54        0.0
55    }
56
57    #[inline]
58    fn reject_nan(self, name: &str) -> ArgumentResult<()> {
59        if self.is_nan() {
60            return Err(ArgumentError::new(format!(
61                "Parameter '{}' must not be NaN",
62                name
63            )));
64        }
65        Ok(())
66    }
67}
68
69impl NumericValue for f64 {
70    #[inline]
71    fn zero() -> Self {
72        0.0
73    }
74
75    #[inline]
76    fn reject_nan(self, name: &str) -> ArgumentResult<()> {
77        if self.is_nan() {
78            return Err(ArgumentError::new(format!(
79                "Parameter '{}' must not be NaN",
80                name
81            )));
82        }
83        Ok(())
84    }
85}
86
87/// Numeric argument validation trait
88///
89/// Provides validation methods for all sortable numeric types, supporting method chaining.
90///
91/// # Features
92///
93/// - Zero-cost abstraction: Same performance as manual checks after compilation
94/// - Method chaining: Can perform multiple validations in sequence
95/// - Type safety: Leverages Rust's type system to ensure correctness
96/// - Clear errors: Provides friendly error messages
97///
98/// # Use Cases
99///
100/// - Validating function parameter validity
101/// - Configuration value range checking
102/// - User input numeric validation
103///
104/// # Examples
105///
106/// Basic usage (returns `ArgumentResult`):
107///
108/// ```rust
109/// use qubit_argument::argument::{NumericArgument, ArgumentResult};
110///
111/// fn set_volume(volume: i32) -> ArgumentResult<()> {
112///     let volume = volume.require_in_closed_range("volume", 0, 100)?;
113///     println!("Volume: {}", volume);
114///     Ok(())
115/// }
116/// ```
117///
118/// Converting to other error types:
119///
120/// ```rust
121/// use qubit_argument::argument::NumericArgument;
122///
123/// fn set_volume(volume: i32) -> Result<(), String> {
124///     let volume = volume
125///         .require_non_negative("volume")
126///         .and_then(|v| v.require_in_closed_range("volume", 0, 100))
127///         .map_err(|e| e.to_string())?;
128///     println!("Volume: {}", volume);
129///     Ok(())
130/// }
131/// ```
132///
133///
134pub trait NumericArgument: Sized {
135    /// Validate that value is zero
136    ///
137    /// # Parameters
138    ///
139    /// * `name` - Parameter name for error message generation
140    ///
141    /// # Returns
142    ///
143    /// Returns `Ok(self)` if value is zero, otherwise returns an error
144    ///
145    /// # Examples
146    ///
147    /// ```rust
148    /// use qubit_argument::argument::NumericArgument;
149    ///
150    /// let value: i32 = 0;
151    /// assert!(value.require_zero("value").is_ok());
152    ///
153    /// let non_zero: i32 = 5;
154    /// assert!(non_zero.require_zero("value").is_err());
155    /// ```
156    fn require_zero(self, name: &str) -> ArgumentResult<Self>;
157
158    /// Validate that value is non-zero
159    ///
160    /// # Parameters
161    ///
162    /// * `name` - Parameter name
163    ///
164    /// # Returns
165    ///
166    /// Returns `Ok(self)` if value is non-zero, otherwise returns an error
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// use qubit_argument::argument::NumericArgument;
172    ///
173    /// let value: i32 = 10;
174    /// assert!(value.require_non_zero("value").is_ok());
175    ///
176    /// let zero: i32 = 0;
177    /// assert!(zero.require_non_zero("value").is_err());
178    /// ```
179    fn require_non_zero(self, name: &str) -> ArgumentResult<Self>;
180
181    /// Validate that value is positive
182    ///
183    /// # Parameters
184    ///
185    /// * `name` - Parameter name
186    ///
187    /// # Returns
188    ///
189    /// Returns `Ok(self)` if value is greater than zero, otherwise returns an error
190    ///
191    /// # Examples
192    ///
193    /// ```rust
194    /// use qubit_argument::argument::NumericArgument;
195    ///
196    /// let value: i32 = 10;
197    /// assert!(value.require_positive("value").is_ok());
198    ///
199    /// let zero: i32 = 0;
200    /// assert!(zero.require_positive("value").is_err());
201    /// ```
202    fn require_positive(self, name: &str) -> ArgumentResult<Self>;
203
204    /// Validate that value is non-negative
205    ///
206    /// # Parameters
207    ///
208    /// * `name` - Parameter name
209    ///
210    /// # Returns
211    ///
212    /// Returns `Ok(self)` if value is non-negative, otherwise returns an error
213    ///
214    /// # Examples
215    ///
216    /// ```rust
217    /// use qubit_argument::argument::NumericArgument;
218    ///
219    /// let value: i32 = 0;
220    /// assert!(value.require_non_negative("value").is_ok());
221    ///
222    /// let negative: i32 = -5;
223    /// assert!(negative.require_non_negative("value").is_err());
224    /// ```
225    fn require_non_negative(self, name: &str) -> ArgumentResult<Self>;
226
227    /// Validate that value is negative
228    ///
229    /// # Parameters
230    ///
231    /// * `name` - Parameter name
232    ///
233    /// # Returns
234    ///
235    /// Returns `Ok(self)` if value is less than zero, otherwise returns an error
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// use qubit_argument::argument::NumericArgument;
241    ///
242    /// let value: i32 = -5;
243    /// assert!(value.require_negative("value").is_ok());
244    ///
245    /// let positive: i32 = 5;
246    /// assert!(positive.require_negative("value").is_err());
247    /// ```
248    fn require_negative(self, name: &str) -> ArgumentResult<Self>;
249
250    /// Validate that value is non-positive
251    ///
252    /// # Parameters
253    ///
254    /// * `name` - Parameter name
255    ///
256    /// # Returns
257    ///
258    /// Returns `Ok(self)` if value is less than or equal to zero, otherwise returns an error
259    ///
260    /// # Examples
261    ///
262    /// ```rust
263    /// use qubit_argument::argument::NumericArgument;
264    ///
265    /// let value: i32 = 0;
266    /// assert!(value.require_non_positive("value").is_ok());
267    ///
268    /// let negative: i32 = -5;
269    /// assert!(negative.require_non_positive("value").is_ok());
270    ///
271    /// let positive: i32 = 5;
272    /// assert!(positive.require_non_positive("value").is_err());
273    /// ```
274    fn require_non_positive(self, name: &str) -> ArgumentResult<Self>;
275
276    /// Validate that value is within closed interval
277    ///
278    /// # Parameters
279    ///
280    /// * `name` - Parameter name
281    /// * `min` - Minimum value (inclusive)
282    /// * `max` - Maximum value (inclusive)
283    ///
284    /// # Returns
285    ///
286    /// Returns `Ok(self)` if value is within [min, max] range, otherwise returns an error
287    ///
288    /// # Examples
289    ///
290    /// ```rust
291    /// use qubit_argument::argument::NumericArgument;
292    ///
293    /// let value = 50;
294    /// assert!(value.require_in_closed_range("value", 0, 100).is_ok());
295    ///
296    /// let out_of_range = 150;
297    /// assert!(out_of_range.require_in_closed_range("value", 0, 100).is_err());
298    /// ```
299    fn require_in_closed_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self>;
300
301    /// Validate that value is within open interval
302    ///
303    /// # Parameters
304    ///
305    /// * `name` - Parameter name
306    /// * `min` - Minimum value (exclusive)
307    /// * `max` - Maximum value (exclusive)
308    ///
309    /// # Returns
310    ///
311    /// Returns `Ok(self)` if value is within (min, max) range, otherwise returns an error
312    ///
313    /// # Examples
314    ///
315    /// ```rust
316    /// use qubit_argument::argument::NumericArgument;
317    ///
318    /// let value = 50;
319    /// assert!(value.require_in_open_range("value", 0, 100).is_ok());
320    ///
321    /// let boundary = 0;
322    /// assert!(boundary.require_in_open_range("value", 0, 100).is_err());
323    /// ```
324    fn require_in_open_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self>;
325
326    /// Validate that value is within left-open right-closed interval
327    ///
328    /// # Parameters
329    ///
330    /// * `name` - Parameter name
331    /// * `min` - Minimum value (exclusive)
332    /// * `max` - Maximum value (inclusive)
333    ///
334    /// # Returns
335    ///
336    /// Returns `Ok(self)` if value is within (min, max] range, otherwise returns an error
337    ///
338    /// # Examples
339    ///
340    /// ```rust
341    /// use qubit_argument::argument::NumericArgument;
342    ///
343    /// let value = 100;
344    /// assert!(value.require_in_left_open_range("value", 0, 100).is_ok());
345    ///
346    /// let min_boundary = 0;
347    /// assert!(min_boundary.require_in_left_open_range("value", 0, 100).is_err());
348    /// ```
349    fn require_in_left_open_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self>;
350
351    /// Validate that value is within left-closed right-open interval
352    ///
353    /// # Parameters
354    ///
355    /// * `name` - Parameter name
356    /// * `min` - Minimum value (inclusive)
357    /// * `max` - Maximum value (exclusive)
358    ///
359    /// # Returns
360    ///
361    /// Returns `Ok(self)` if value is within [min, max) range, otherwise returns an error
362    ///
363    /// # Examples
364    ///
365    /// ```rust
366    /// use qubit_argument::argument::NumericArgument;
367    ///
368    /// let value = 0;
369    /// assert!(value.require_in_right_open_range("value", 0, 100).is_ok());
370    ///
371    /// let max_boundary = 100;
372    /// assert!(max_boundary.require_in_right_open_range("value", 0, 100).is_err());
373    /// ```
374    fn require_in_right_open_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self>;
375
376    /// Validate that value is less than specified value
377    ///
378    /// # Parameters
379    ///
380    /// * `name` - Parameter name
381    /// * `max` - Maximum value (exclusive)
382    ///
383    /// # Returns
384    ///
385    /// Returns `Ok(self)` if value is less than max, otherwise returns an error
386    ///
387    /// # Examples
388    ///
389    /// ```rust
390    /// use qubit_argument::argument::NumericArgument;
391    ///
392    /// let value = 50;
393    /// assert!(value.require_less("value", 100).is_ok());
394    ///
395    /// let boundary = 100;
396    /// assert!(boundary.require_less("value", 100).is_err());
397    /// ```
398    fn require_less(self, name: &str, max: Self) -> ArgumentResult<Self>;
399
400    /// Validate that value is less than or equal to specified value
401    ///
402    /// # Parameters
403    ///
404    /// * `name` - Parameter name
405    /// * `max` - Maximum value (inclusive)
406    ///
407    /// # Returns
408    ///
409    /// Returns `Ok(self)` if value is less than or equal to max, otherwise returns an error
410    ///
411    /// # Examples
412    ///
413    /// ```rust
414    /// use qubit_argument::argument::NumericArgument;
415    ///
416    /// let value = 100;
417    /// assert!(value.require_less_equal("value", 100).is_ok());
418    ///
419    /// let over = 101;
420    /// assert!(over.require_less_equal("value", 100).is_err());
421    /// ```
422    fn require_less_equal(self, name: &str, max: Self) -> ArgumentResult<Self>;
423
424    /// Validate that value is greater than specified value
425    ///
426    /// # Parameters
427    ///
428    /// * `name` - Parameter name
429    /// * `min` - Minimum value (exclusive)
430    ///
431    /// # Returns
432    ///
433    /// Returns `Ok(self)` if value is greater than min, otherwise returns an error
434    ///
435    /// # Examples
436    ///
437    /// ```rust
438    /// use qubit_argument::argument::NumericArgument;
439    ///
440    /// let value = 50;
441    /// assert!(value.require_greater("value", 0).is_ok());
442    ///
443    /// let boundary = 0;
444    /// assert!(boundary.require_greater("value", 0).is_err());
445    /// ```
446    fn require_greater(self, name: &str, min: Self) -> ArgumentResult<Self>;
447
448    /// Validate that value is greater than or equal to specified value
449    ///
450    /// # Parameters
451    ///
452    /// * `name` - Parameter name
453    /// * `min` - Minimum value (inclusive)
454    ///
455    /// # Returns
456    ///
457    /// Returns `Ok(self)` if value is greater than or equal to min, otherwise returns an error
458    ///
459    /// # Examples
460    ///
461    /// ```rust
462    /// use qubit_argument::argument::NumericArgument;
463    ///
464    /// let value = 0;
465    /// assert!(value.require_greater_equal("value", 0).is_ok());
466    ///
467    /// let under = -1;
468    /// assert!(under.require_greater_equal("value", 0).is_err());
469    /// ```
470    fn require_greater_equal(self, name: &str, min: Self) -> ArgumentResult<Self>;
471}
472
473/// Implement numeric argument validation for all ordered displayable types
474///
475/// Automatically provides validation functionality for all standard numeric
476/// types: i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize,
477/// f32, and f64.
478impl<T> NumericArgument for T
479where
480    T: NumericValue,
481{
482    #[inline]
483    fn require_zero(self, name: &str) -> ArgumentResult<Self> {
484        self.reject_nan(name)?;
485        if self != T::zero() {
486            return Err(ArgumentError::new(format!(
487                "Parameter '{}' must be zero but was: {}",
488                name, self
489            )));
490        }
491        Ok(self)
492    }
493
494    #[inline]
495    fn require_non_zero(self, name: &str) -> ArgumentResult<Self> {
496        self.reject_nan(name)?;
497        if self == T::zero() {
498            return Err(ArgumentError::new(format!(
499                "Parameter '{}' cannot be zero",
500                name
501            )));
502        }
503        Ok(self)
504    }
505
506    #[inline]
507    fn require_positive(self, name: &str) -> ArgumentResult<Self> {
508        self.reject_nan(name)?;
509        if self <= T::zero() {
510            return Err(ArgumentError::new(format!(
511                "Parameter '{}' must be positive but was: {}",
512                name, self
513            )));
514        }
515        Ok(self)
516    }
517
518    #[inline]
519    fn require_non_negative(self, name: &str) -> ArgumentResult<Self> {
520        self.reject_nan(name)?;
521        if self < T::zero() {
522            return Err(ArgumentError::new(format!(
523                "Parameter '{}' must be non-negative but was: {}",
524                name, self
525            )));
526        }
527        Ok(self)
528    }
529
530    #[inline]
531    fn require_negative(self, name: &str) -> ArgumentResult<Self> {
532        self.reject_nan(name)?;
533        if self >= T::zero() {
534            return Err(ArgumentError::new(format!(
535                "Parameter '{}' must be negative but was: {}",
536                name, self
537            )));
538        }
539        Ok(self)
540    }
541
542    #[inline]
543    fn require_non_positive(self, name: &str) -> ArgumentResult<Self> {
544        self.reject_nan(name)?;
545        if self > T::zero() {
546            return Err(ArgumentError::new(format!(
547                "Parameter '{}' must be non-positive but was: {}",
548                name, self
549            )));
550        }
551        Ok(self)
552    }
553
554    #[inline]
555    fn require_in_closed_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self> {
556        self.reject_nan(name)?;
557        min.reject_nan("min")?;
558        max.reject_nan("max")?;
559        if min > max {
560            return Err(ArgumentError::new(format!(
561                "Parameter '{}' has invalid range: min {} is greater than max {}",
562                name, min, max
563            )));
564        }
565        if self < min || self > max {
566            return Err(ArgumentError::new(format!(
567                "Parameter '{}' must be in range [{}, {}] but was: {}",
568                name, min, max, self
569            )));
570        }
571        Ok(self)
572    }
573
574    #[inline]
575    fn require_in_open_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self> {
576        self.reject_nan(name)?;
577        min.reject_nan("min")?;
578        max.reject_nan("max")?;
579        if min >= max {
580            return Err(ArgumentError::new(format!(
581                "Parameter '{}' has invalid range: min {} must be less than max {}",
582                name, min, max
583            )));
584        }
585        if self <= min || self >= max {
586            return Err(ArgumentError::new(format!(
587                "Parameter '{}' must be in range ({}, {}) but was: {}",
588                name, min, max, self
589            )));
590        }
591        Ok(self)
592    }
593
594    #[inline]
595    fn require_in_left_open_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self> {
596        self.reject_nan(name)?;
597        min.reject_nan("min")?;
598        max.reject_nan("max")?;
599        if min > max {
600            return Err(ArgumentError::new(format!(
601                "Parameter '{}' has invalid range: min {} is greater than max {}",
602                name, min, max
603            )));
604        }
605        if self <= min || self > max {
606            return Err(ArgumentError::new(format!(
607                "Parameter '{}' must be in range ({}, {}] but was: {}",
608                name, min, max, self
609            )));
610        }
611        Ok(self)
612    }
613
614    #[inline]
615    fn require_in_right_open_range(self, name: &str, min: Self, max: Self) -> ArgumentResult<Self> {
616        self.reject_nan(name)?;
617        min.reject_nan("min")?;
618        max.reject_nan("max")?;
619        if min > max {
620            return Err(ArgumentError::new(format!(
621                "Parameter '{}' has invalid range: min {} is greater than max {}",
622                name, min, max
623            )));
624        }
625        if self < min || self >= max {
626            return Err(ArgumentError::new(format!(
627                "Parameter '{}' must be in range [{}, {}) but was: {}",
628                name, min, max, self
629            )));
630        }
631        Ok(self)
632    }
633
634    #[inline]
635    fn require_less(self, name: &str, max: Self) -> ArgumentResult<Self> {
636        self.reject_nan(name)?;
637        max.reject_nan("max")?;
638        if self >= max {
639            return Err(ArgumentError::new(format!(
640                "Parameter '{}' must be less than {} but was: {}",
641                name, max, self
642            )));
643        }
644        Ok(self)
645    }
646
647    #[inline]
648    fn require_less_equal(self, name: &str, max: Self) -> ArgumentResult<Self> {
649        self.reject_nan(name)?;
650        max.reject_nan("max")?;
651        if self > max {
652            return Err(ArgumentError::new(format!(
653                "Parameter '{}' must be less than or equal to {} but was: {}",
654                name, max, self
655            )));
656        }
657        Ok(self)
658    }
659
660    #[inline]
661    fn require_greater(self, name: &str, min: Self) -> ArgumentResult<Self> {
662        self.reject_nan(name)?;
663        min.reject_nan("min")?;
664        if self <= min {
665            return Err(ArgumentError::new(format!(
666                "Parameter '{}' must be greater than {} but was: {}",
667                name, min, self
668            )));
669        }
670        Ok(self)
671    }
672
673    #[inline]
674    fn require_greater_equal(self, name: &str, min: Self) -> ArgumentResult<Self> {
675        self.reject_nan(name)?;
676        min.reject_nan("min")?;
677        if self < min {
678            return Err(ArgumentError::new(format!(
679                "Parameter '{}' must be greater than or equal to {} but was: {}",
680                name, min, self
681            )));
682        }
683        Ok(self)
684    }
685}
686
687/// Comparison argument validation
688///
689/// Provides comparison validation functionality between two arguments.
690///
691/// # Examples
692///
693/// ```rust
694/// use qubit_argument::argument::require_equal;
695///
696/// let result = require_equal("width", 100, "height", 100);
697/// assert!(result.is_ok());
698///
699/// let result = require_equal("width", 100, "height", 200);
700/// assert!(result.is_err());
701/// ```
702///
703#[inline]
704pub fn require_equal<T>(name1: &str, value1: T, name2: &str, value2: T) -> ArgumentResult<()>
705where
706    T: PartialEq + Display,
707{
708    if value1 != value2 {
709        return Err(ArgumentError::new(format!(
710            "Parameter '{}' ({}) must equal parameter '{}' ({})",
711            name1, value1, name2, value2
712        )));
713    }
714    Ok(())
715}
716
717/// Validate that two arguments are not equal
718///
719/// # Parameters
720///
721/// * `name1` - First parameter name
722/// * `value1` - First parameter value
723/// * `name2` - Second parameter name
724/// * `value2` - Second parameter value
725///
726/// # Examples
727///
728/// ```rust
729/// use qubit_argument::argument::require_not_equal;
730///
731/// let result = require_not_equal("min", 0, "max", 100);
732/// assert!(result.is_ok());
733/// ```
734///
735#[inline]
736pub fn require_not_equal<T>(name1: &str, value1: T, name2: &str, value2: T) -> ArgumentResult<()>
737where
738    T: PartialEq + Display,
739{
740    if value1 == value2 {
741        return Err(ArgumentError::new(format!(
742            "Parameters '{}' and '{}' cannot be equal (both are: {})",
743            name1, name2, value1
744        )));
745    }
746    Ok(())
747}