qubit_budget/resource/limit/resource_limit.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//! Defines immutable point limits bound to resource identities.
9
10use std::fmt::Debug;
11
12use crate::resource::BudgetError;
13use crate::resource::LimitExceededError;
14use crate::resource::MeasuredBudgetError;
15use crate::resource::Observation;
16use crate::resource::ResourceQuantity;
17
18/// An inclusive immutable maximum for one resource measurement.
19///
20/// # Type Parameters
21///
22/// * `R` - Caller-defined resource value retained in limit failures.
23/// * `Q` - Copyable measurement value used by the maximum and checks.
24///
25/// # Examples
26///
27/// ```
28/// use qubit_budget::ResourceLimit;
29///
30/// let limit = ResourceLimit::new("payload bytes", 8_u64);
31/// limit.check(8).expect("the inclusive maximum should fit");
32/// assert!(limit.check(9).is_err());
33/// ```
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct ResourceLimit<R, Q = u64>
36where
37 Q: Copy + Debug,
38{
39 /// Resource bound to this limit.
40 resource: R,
41
42 /// Inclusive maximum accepted by this limit.
43 maximum: Q,
44}
45
46impl<R, Q> ResourceLimit<R, Q>
47where
48 Q: Copy + Debug,
49{
50 /// Creates an immutable limit bound to `resource`.
51 ///
52 /// # Parameters
53 ///
54 /// * `resource` - Domain resource reported when the limit is exceeded.
55 /// * `maximum` - Inclusive maximum measurement accepted by [`Self::check`].
56 ///
57 /// # Returns
58 ///
59 /// A limit that accepts measurements less than or equal to `maximum`.
60 #[inline]
61 #[must_use]
62 pub const fn new(resource: R, maximum: Q) -> Self {
63 Self { resource, maximum }
64 }
65
66 /// Returns the resource bound to this limit.
67 ///
68 /// # Returns
69 ///
70 /// Returns the resource bound to this limit.
71 #[inline(always)]
72 #[must_use]
73 pub const fn resource(&self) -> &R {
74 &self.resource
75 }
76
77 /// Returns this limit's inclusive maximum measurement.
78 ///
79 /// # Returns
80 ///
81 /// Returns this limit's inclusive maximum measurement.
82 #[inline(always)]
83 #[must_use]
84 pub const fn maximum(&self) -> Q {
85 self.maximum
86 }
87
88 /// Checks whether `actual` is within the inclusive maximum.
89 ///
90 /// # Parameters
91 ///
92 /// * `actual` - Observed measurement to validate.
93 ///
94 /// # Returns
95 ///
96 /// `Ok(())` when `actual <= maximum`; otherwise returns
97 /// [`LimitExceededError`] containing the resource, observed value,
98 /// and maximum. This method does not mutate the limit.
99 ///
100 /// # Errors
101 ///
102 /// Returns [`LimitExceededError`] when `actual` is greater than
103 /// this limit's maximum.
104 #[inline]
105 #[must_use = "the limit check result must be handled"]
106 pub fn check(&self, actual: Q) -> Result<(), LimitExceededError<R, Q>>
107 where
108 R: Clone,
109 Q: Ord,
110 {
111 if actual > self.maximum {
112 Err(LimitExceededError {
113 resource: self.resource.clone(),
114 observed: Observation::Exact(actual),
115 maximum: self.maximum,
116 })
117 } else {
118 Ok(())
119 }
120 }
121}
122
123impl<R, Q> ResourceLimit<R, Q>
124where
125 Q: ResourceQuantity,
126{
127 /// Checks a machine-sized measurement without truncating it.
128 ///
129 /// # Parameters
130 ///
131 /// * `actual` - Native measurement to convert and compare with the limit.
132 ///
133 /// # Returns
134 ///
135 /// `Ok(())` when the converted measurement fits this limit.
136 ///
137 /// # Errors
138 ///
139 /// Returns [`MeasuredBudgetError::Quantity`] when `actual` cannot be
140 /// represented by `Q`, or [`MeasuredBudgetError::Budget`] when the
141 /// converted value exceeds this limit.
142 #[inline]
143 #[must_use = "the limit check result must be handled"]
144 pub fn check_usize(&self, actual: usize) -> Result<(), MeasuredBudgetError<R, Q>>
145 where
146 R: Clone,
147 {
148 let actual =
149 Q::try_from_usize(actual).map_err(|source| MeasuredBudgetError::quantity(self.resource.clone(), source))?;
150 self.check(actual).map_err(MeasuredBudgetError::from)
151 }
152
153 /// Checks a 64-bit measurement without truncating it.
154 ///
155 /// # Parameters
156 ///
157 /// * `actual` - 64-bit measurement to convert and compare with the limit.
158 ///
159 /// # Returns
160 ///
161 /// `Ok(())` when the converted measurement fits this limit.
162 ///
163 /// # Errors
164 ///
165 /// Returns [`MeasuredBudgetError::Quantity`] when `actual` cannot be
166 /// represented by `Q`, or [`MeasuredBudgetError::Budget`] when the
167 /// converted value exceeds this limit.
168 #[inline]
169 #[must_use = "the limit check result must be handled"]
170 pub fn check_u64(&self, actual: u64) -> Result<(), MeasuredBudgetError<R, Q>>
171 where
172 R: Clone,
173 {
174 let actual =
175 Q::try_from_u64(actual).map_err(|source| MeasuredBudgetError::quantity(self.resource.clone(), source))?;
176 self.check(actual).map_err(MeasuredBudgetError::from)
177 }
178}
179
180/// Checks an optional point limit.
181///
182/// # Type Parameters
183///
184/// * `R` - Caller-defined resource identity retained by limits and errors.
185/// * `Q` - Exact unsigned quantity used for measurements and accounting.
186///
187/// # Parameters
188///
189/// * `limit` - Configured limit, or `None` when the dimension is unconfigured.
190/// * `actual` - Observed measurement to validate.
191///
192/// # Returns
193///
194/// `Ok(())` when `limit` is `None`, or when [`ResourceLimit::check`] accepts
195/// `actual`.
196///
197/// # Errors
198///
199/// Returns [`BudgetError::LimitExceeded`] when a configured limit rejects
200/// `actual`.
201#[inline]
202pub(crate) fn check_limit<R, Q>(limit: Option<&ResourceLimit<R, Q>>, actual: Q) -> Result<(), BudgetError<R, Q>>
203where
204 R: Clone,
205 Q: Copy + Debug + Ord,
206{
207 match limit {
208 Some(limit) => limit.check(actual).map_err(BudgetError::from),
209 None => Ok(()),
210 }
211}