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}