qubit_budget/resource/error/measured_budget_error.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines failures produced while measuring resource-limited values.
9// qubit-style: allow source-test-pair
10
11use std::fmt::Debug;
12
13use thiserror::Error;
14
15use crate::resource::BudgetError;
16use crate::resource::InsufficientBudgetError;
17use crate::resource::LimitExceededError;
18use crate::resource::QuantityConversionError;
19
20/// Error returned when native measurement or budget validation rejects a value.
21///
22/// # Type Parameters
23///
24/// * `R` - Caller-defined resource identity retained by limits and errors.
25/// * `Q` - Exact unsigned quantity used for measurements and accounting.
26///
27/// # Examples
28///
29/// ```
30/// use qubit_budget::MeasuredBudgetError;
31/// use qubit_budget::QuantityConversionError;
32/// use qubit_budget::QuantityMeasurement;
33///
34/// let source = QuantityConversionError::new(QuantityMeasurement::Usize(256), "u8");
35/// let error = MeasuredBudgetError::<_, u8>::quantity("bytes", source);
36/// assert!(error.quantity_error().is_some());
37/// ```
38#[must_use]
39#[derive(Clone, Debug, Error)]
40pub enum MeasuredBudgetError<R, Q = u64>
41where
42 Q: Copy + Debug,
43{
44 /// A native measurement could not fit the configured quantity type.
45 #[error("resource {resource:?} has an unrepresentable measurement: {source}")]
46 Quantity {
47 /// Resource associated with the rejected measurement.
48 resource: R,
49 /// Native quantity conversion failure.
50 #[source]
51 source: QuantityConversionError,
52 },
53
54 /// A representable measurement exceeded its configured resource budget.
55 #[error(transparent)]
56 Budget(
57 /// Exact point-limit or cumulative-budget failure.
58 #[from]
59 BudgetError<R, Q>,
60 ),
61}
62
63impl<R, Q> MeasuredBudgetError<R, Q>
64where
65 Q: Copy + Debug,
66{
67 /// Creates a failure for a native measurement that did not fit `Q`.
68 ///
69 /// # Parameters
70 ///
71 /// * `resource` - Resource being measured.
72 /// * `source` - Exact native quantity conversion failure.
73 ///
74 /// # Returns
75 ///
76 /// A quantity representation failure retaining its resource identity.
77 #[inline(always)]
78 pub const fn quantity(resource: R, source: QuantityConversionError) -> Self {
79 Self::Quantity { resource, source }
80 }
81
82 /// Returns the contained budget failure when the measurement fit `Q`.
83 ///
84 /// # Returns
85 ///
86 /// `Some` for [`Self::Budget`], or `None` for [`Self::Quantity`].
87 #[must_use]
88 #[inline(always)]
89 pub const fn budget_error(&self) -> Option<&BudgetError<R, Q>> {
90 match self {
91 Self::Budget(error) => Some(error),
92 Self::Quantity { .. } => None,
93 }
94 }
95
96 /// Returns the native quantity conversion failure, when present.
97 ///
98 /// # Returns
99 ///
100 /// `Some` for [`Self::Quantity`], or `None` for [`Self::Budget`].
101 #[must_use]
102 #[inline(always)]
103 pub const fn quantity_error(&self) -> Option<&QuantityConversionError> {
104 match self {
105 Self::Quantity { source, .. } => Some(source),
106 Self::Budget(_) => None,
107 }
108 }
109
110 /// Returns the resource associated with this failure.
111 ///
112 /// The resource is present for both budget validation and quantity
113 /// conversion failures, so callers do not need to match the error variant
114 /// merely to attach resource context.
115 ///
116 /// # Returns
117 ///
118 /// Returns the resource associated with this failure.
119 #[must_use]
120 #[inline(always)]
121 pub const fn resource(&self) -> &R {
122 match self {
123 Self::Quantity { resource, .. } => resource,
124 Self::Budget(error) => error.resource(),
125 }
126 }
127
128 /// Consumes this failure and returns its associated resource.
129 ///
130 /// # Returns
131 ///
132 /// Consumes this failure and returns its associated resource.
133 #[inline(always)]
134 #[must_use]
135 pub fn into_resource(self) -> R {
136 match self {
137 Self::Quantity { resource, .. } => resource,
138 Self::Budget(error) => error.into_resource(),
139 }
140 }
141}
142
143impl<R, Q> From<LimitExceededError<R, Q>> for MeasuredBudgetError<R, Q>
144where
145 Q: Copy + Debug,
146{
147 /// Wraps a point-limit failure in a measured-budget failure.
148 ///
149 /// # Parameters
150 ///
151 /// * `error` - Precise point-limit failure to wrap.
152 ///
153 /// # Returns
154 ///
155 /// Wraps a point-limit failure in a measured-budget failure.
156 #[inline(always)]
157 fn from(error: LimitExceededError<R, Q>) -> Self {
158 Self::Budget(error.into())
159 }
160}
161
162impl<R, Q> From<InsufficientBudgetError<R, Q>> for MeasuredBudgetError<R, Q>
163where
164 Q: Copy + Debug,
165{
166 /// Wraps a cumulative-budget failure in a measured-budget failure.
167 ///
168 /// # Parameters
169 ///
170 /// * `error` - Precise cumulative-budget failure to wrap.
171 ///
172 /// # Returns
173 ///
174 /// Wraps a cumulative-budget failure in a measured-budget failure.
175 #[inline(always)]
176 fn from(error: InsufficientBudgetError<R, Q>) -> Self {
177 Self::Budget(error.into())
178 }
179}