Skip to main content

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}