qubit_budget/json/decode/json_decode_session.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//! Tracks mutable accounting for one JSON decoding operation.
9
10use super::JsonDecodeAttempt;
11use super::JsonDecodeLimits;
12use super::internal::DecodeStorage;
13use crate::json::JsonResource;
14use crate::json::JsonValueBudget;
15use crate::resource::ResourceBudget;
16use crate::resource::ResourceQuantity;
17
18/// Mutable resource accounting for one JSON decoding operation.
19///
20/// Use [`Self::from_limits`] for a session that owns budgets created from
21/// immutable limits, or one of the `borrowing_*` constructors when the caller
22/// owns the budgets. Create an attempt with [`Self::begin_value`] for each
23/// complete value; input charges are immediate, while value accounting is
24/// committed by [`JsonDecodeAttempt::commit`].
25///
26/// # Type Parameters
27///
28/// * `R` - Caller-defined resource identity retained by limits and errors.
29/// * `Q` - Exact unsigned quantity used for measurements and accounting.
30///
31/// # Examples
32///
33/// ```
34/// use qubit_budget::json::JsonDecodeLimits;
35/// use qubit_budget::json::JsonDecodeSession;
36/// use qubit_budget::json::JsonMeasurement;
37///
38/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
39/// let limits = JsonDecodeLimits::builder()
40/// .max_input_bytes(4_usize)
41/// .max_nodes(1_usize)
42/// .build();
43/// let mut session = JsonDecodeSession::from_limits(limits);
44/// let mut attempt = session.begin_value();
45/// attempt
46/// .try_consume_input_bytes(4)
47/// .expect("the input should fit");
48/// attempt
49/// .try_admit(JsonMeasurement::Null { depth: 1 })
50/// .expect("the value should fit");
51/// attempt.commit()?;
52/// assert_eq!(session.input_budget().expect("input budget").used(), 4);
53/// # Ok(()) }
54/// ```
55#[derive(Debug)]
56pub struct JsonDecodeSession<'a, R = JsonResource, Q = usize>
57where
58 Q: ResourceQuantity,
59{
60 /// Owned or borrowed budgets backing this decode operation.
61 storage: DecodeStorage<'a, R, Q>,
62}
63
64impl<'a, R, Q> JsonDecodeSession<'a, R, Q>
65where
66 R: Clone,
67 Q: ResourceQuantity,
68{
69 /// Creates a session borrowing only a caller-owned value budget.
70 ///
71 /// # Parameters
72 ///
73 /// * `value` - Caller-owned JSON value budget to update after a successful
74 /// decode attempt.
75 ///
76 /// # Returns
77 ///
78 /// Creates a session borrowing only a caller-owned value budget.
79 #[inline]
80 #[must_use]
81 pub fn borrowing_value(value: &'a mut JsonValueBudget<R, Q>) -> Self {
82 Self {
83 storage: DecodeStorage::Borrowed {
84 input: None,
85 normalized_input: None,
86 value,
87 },
88 }
89 }
90
91 /// Creates a session borrowing caller-owned raw-input and value budgets.
92 ///
93 /// # Parameters
94 ///
95 /// * `input` - Input supplied to this operation.
96 /// * `value` - Caller-owned JSON value budget to update after a successful
97 /// decode attempt.
98 ///
99 /// # Returns
100 ///
101 /// Creates a session borrowing caller-owned raw-input and value budgets.
102 #[inline]
103 #[must_use]
104 pub fn borrowing_input(input: &'a mut ResourceBudget<R, Q>, value: &'a mut JsonValueBudget<R, Q>) -> Self {
105 Self {
106 storage: DecodeStorage::Borrowed {
107 input: Some(input),
108 normalized_input: None,
109 value,
110 },
111 }
112 }
113
114 /// Creates a session borrowing all caller-owned decode budgets.
115 ///
116 /// # Parameters
117 ///
118 /// * `input` - Input supplied to this operation.
119 /// * `normalized_input` - Optional caller-owned normalized-input budget.
120 /// * `value` - Caller-owned JSON value budget to update after a successful
121 /// decode attempt.
122 ///
123 /// # Returns
124 ///
125 /// Creates a session borrowing all caller-owned decode budgets.
126 #[inline]
127 #[must_use]
128 pub fn borrowing_all(
129 input: &'a mut ResourceBudget<R, Q>,
130 normalized_input: &'a mut ResourceBudget<R, Q>,
131 value: &'a mut JsonValueBudget<R, Q>,
132 ) -> Self {
133 Self {
134 storage: DecodeStorage::Borrowed {
135 input: Some(input),
136 normalized_input: Some(normalized_input),
137 value,
138 },
139 }
140 }
141
142 /// Starts accounting for one complete JSON value.
143 ///
144 /// The returned attempt charges raw and normalized input immediately, but
145 /// publishes staged JSON value accounting only after a successful `commit`.
146 /// A value-admission failure poisons commit; dropping the attempt rolls
147 /// back only the staged value state.
148 ///
149 /// # Returns
150 ///
151 /// Starts accounting for one complete JSON value.
152 #[must_use]
153 pub fn begin_value(&mut self) -> JsonDecodeAttempt<'_, R, Q> {
154 let (input, normalized_input, value) = self.storage.split();
155 JsonDecodeAttempt::new(input, normalized_input, value.transaction())
156 }
157
158 /// Returns the raw input budget when configured.
159 ///
160 /// # Returns
161 ///
162 /// Returns the raw input budget when configured.
163 ///
164 /// `None` indicates that the corresponding limit or budget dimension is
165 /// unconfigured.
166 #[must_use]
167 #[inline(always)]
168 pub fn input_budget(&self) -> Option<&ResourceBudget<R, Q>> {
169 match &self.storage {
170 DecodeStorage::Owned { input, .. } => input.as_ref(),
171 DecodeStorage::Borrowed { input, .. } => input.as_deref(),
172 }
173 }
174
175 /// Returns the configured raw input-byte maximum.
176 ///
177 /// # Returns
178 ///
179 /// Returns the configured raw input-byte maximum.
180 ///
181 /// `None` indicates that the corresponding limit or budget dimension is
182 /// unconfigured.
183 #[must_use]
184 #[inline(always)]
185 pub fn max_input_bytes(&self) -> Option<Q> {
186 self.input_budget().map(ResourceBudget::limit)
187 }
188
189 /// Returns the configured normalized input-byte maximum.
190 ///
191 /// # Returns
192 ///
193 /// Returns the configured normalized input-byte maximum.
194 ///
195 /// `None` indicates that the corresponding limit or budget dimension is
196 /// unconfigured.
197 #[must_use]
198 #[inline(always)]
199 pub fn max_normalized_input_bytes(&self) -> Option<Q> {
200 self.normalized_input_budget().map(ResourceBudget::limit)
201 }
202
203 /// Returns the normalized input budget when configured.
204 ///
205 /// # Returns
206 ///
207 /// Returns the normalized input budget when configured.
208 ///
209 /// `None` indicates that the corresponding limit or budget dimension is
210 /// unconfigured.
211 #[must_use]
212 #[inline(always)]
213 pub fn normalized_input_budget(&self) -> Option<&ResourceBudget<R, Q>> {
214 match &self.storage {
215 DecodeStorage::Owned { normalized_input, .. } => normalized_input.as_ref(),
216 DecodeStorage::Borrowed { normalized_input, .. } => normalized_input.as_deref(),
217 }
218 }
219
220 /// Returns the value budget for read-only inspection.
221 ///
222 /// # Returns
223 ///
224 /// Returns the value budget for read-only inspection.
225 #[must_use]
226 #[inline(always)]
227 pub fn value_budget(&self) -> &JsonValueBudget<R, Q> {
228 match &self.storage {
229 DecodeStorage::Owned { value, .. } => value,
230 DecodeStorage::Borrowed { value, .. } => value,
231 }
232 }
233}
234
235impl<R, Q> JsonDecodeSession<'static, R, Q>
236where
237 R: Clone,
238 Q: ResourceQuantity,
239{
240 /// Creates a session that owns budgets initialized from immutable limits.
241 ///
242 /// # Parameters
243 ///
244 /// * `limits` - Immutable decoding limits used to initialize owned
245 /// accounting budgets.
246 ///
247 /// # Returns
248 ///
249 /// Creates a session that owns budgets initialized from immutable limits.
250 #[inline]
251 #[must_use]
252 pub fn from_limits(limits: JsonDecodeLimits<R, Q>) -> Self {
253 let input = limits.input_bytes_limit().cloned().map(ResourceBudget::from_limit);
254 let normalized_input = limits
255 .normalized_input_bytes_limit()
256 .cloned()
257 .map(ResourceBudget::from_limit);
258 let value = JsonValueBudget::new(limits.into_value_limits());
259 Self {
260 storage: DecodeStorage::Owned {
261 input,
262 normalized_input,
263 value,
264 },
265 }
266 }
267}