Skip to main content

qubit_argument/argument/
duration_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 duration arguments.
9
10use std::time::Duration;
11
12use crate::argument::{
13    ArgumentError,
14    ArgumentErrorKind,
15    ArgumentResult,
16    ArgumentValue,
17    ComparisonConstraint,
18    sealed::Sealed,
19};
20
21/// Validates standard-library duration arguments without losing their unit.
22///
23/// Every successful method returns the original duration. Comparison failures
24/// retain both the actual duration and bound as unit-bearing
25/// [`ArgumentValue`] values.
26///
27/// The trait is sealed and implemented only for [`Duration`].
28pub trait DurationArgument: Sealed + Sized {
29    /// Requires this duration to be strictly greater than zero.
30    ///
31    /// Success returns the original duration. A zero duration returns
32    /// [`ArgumentErrorKind::Comparison`] with a `GreaterThan(Duration::ZERO)`
33    /// constraint at `path`.
34    fn require_positive(self, path: &str) -> ArgumentResult<Self>;
35
36    /// Requires this duration to be strictly less than `bound`.
37    ///
38    /// Success returns the original duration. An unsatisfied comparison
39    /// returns [`ArgumentErrorKind::Comparison`] at `path` with the exact
40    /// duration bound.
41    fn require_less_than(self, path: &str, bound: Self)
42    -> ArgumentResult<Self>;
43
44    /// Requires this duration to be less than or equal to `bound`.
45    ///
46    /// Success returns the original duration. An unsatisfied comparison
47    /// returns [`ArgumentErrorKind::Comparison`] at `path` with the exact
48    /// duration bound.
49    fn require_at_most(self, path: &str, bound: Self) -> ArgumentResult<Self>;
50
51    /// Requires this duration to be strictly greater than `bound`.
52    ///
53    /// Success returns the original duration. An unsatisfied comparison
54    /// returns [`ArgumentErrorKind::Comparison`] at `path` with the exact
55    /// duration bound.
56    fn require_greater_than(
57        self,
58        path: &str,
59        bound: Self,
60    ) -> ArgumentResult<Self>;
61
62    /// Requires this duration to be greater than or equal to `bound`.
63    ///
64    /// Success returns the original duration. An unsatisfied comparison
65    /// returns [`ArgumentErrorKind::Comparison`] at `path` with the exact
66    /// duration bound.
67    fn require_at_least(self, path: &str, bound: Self) -> ArgumentResult<Self>;
68}
69
70impl DurationArgument for Duration {
71    /// Requires a nonzero duration and preserves its exact value.
72    #[inline]
73    fn require_positive(self, path: &str) -> ArgumentResult<Self> {
74        validate_duration_comparison(
75            self,
76            path,
77            Duration::ZERO,
78            ComparisonConstraint::GreaterThan(ArgumentValue::from(
79                Duration::ZERO,
80            )),
81            |actual, bound| actual > bound,
82        )
83    }
84
85    /// Requires a duration strictly below the supplied bound.
86    #[inline]
87    fn require_less_than(
88        self,
89        path: &str,
90        bound: Self,
91    ) -> ArgumentResult<Self> {
92        validate_duration_comparison(
93            self,
94            path,
95            bound,
96            ComparisonConstraint::LessThan(ArgumentValue::from(bound)),
97            |actual, bound| actual < bound,
98        )
99    }
100
101    /// Requires a duration at or below the supplied bound.
102    #[inline]
103    fn require_at_most(self, path: &str, bound: Self) -> ArgumentResult<Self> {
104        validate_duration_comparison(
105            self,
106            path,
107            bound,
108            ComparisonConstraint::AtMost(ArgumentValue::from(bound)),
109            |actual, bound| actual <= bound,
110        )
111    }
112
113    /// Requires a duration strictly above the supplied bound.
114    #[inline]
115    fn require_greater_than(
116        self,
117        path: &str,
118        bound: Self,
119    ) -> ArgumentResult<Self> {
120        validate_duration_comparison(
121            self,
122            path,
123            bound,
124            ComparisonConstraint::GreaterThan(ArgumentValue::from(bound)),
125            |actual, bound| actual > bound,
126        )
127    }
128
129    /// Requires a duration at or above the supplied bound.
130    #[inline]
131    fn require_at_least(self, path: &str, bound: Self) -> ArgumentResult<Self> {
132        validate_duration_comparison(
133            self,
134            path,
135            bound,
136            ComparisonConstraint::AtLeast(ArgumentValue::from(bound)),
137            |actual, bound| actual >= bound,
138        )
139    }
140}
141
142/// Applies one duration comparison and preserves the actual value on success.
143///
144/// `actual` and `bound` are passed to `predicate` exactly once. If the
145/// predicate returns `false`, the error records `actual`, `constraint`, and
146/// `path` without converting the duration to a unitless number.
147fn validate_duration_comparison<F>(
148    actual: Duration,
149    path: &str,
150    bound: Duration,
151    constraint: ComparisonConstraint,
152    predicate: F,
153) -> ArgumentResult<Duration>
154where
155    F: FnOnce(Duration, Duration) -> bool,
156{
157    if predicate(actual, bound) {
158        Ok(actual)
159    } else {
160        Err(ArgumentError::new(
161            path,
162            ArgumentErrorKind::Comparison {
163                actual: ArgumentValue::from(actual),
164                constraint,
165            },
166        ))
167    }
168}