qubit_budget/json/encode/json_encode_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 encoding operation.
9
10use super::JsonEncodeAttempt;
11use super::JsonEncodeLimits;
12use super::internal::EncodeStorage;
13use crate::json::JsonResource;
14use crate::json::JsonValueBudget;
15use crate::resource::ResourceBudget;
16use crate::resource::ResourceQuantity;
17
18/// Mutable resource accounting for one JSON encoding operation.
19///
20/// Use [`Self::from_limits`] for a session that owns budgets created from
21/// immutable limits, or [`Self::borrowing_output`] when the caller owns the
22/// output and value budgets. Accepted output bytes are charged immediately;
23/// value measurements are staged until [`JsonEncodeAttempt::commit`].
24///
25/// # Type Parameters
26///
27/// * `R` - Caller-defined resource identity retained by limits and errors.
28/// * `Q` - Exact unsigned quantity used for measurements and accounting.
29///
30/// # Examples
31///
32/// ```
33/// use qubit_budget::json::JsonEncodeLimits;
34/// use qubit_budget::json::JsonEncodeSession;
35/// use qubit_budget::json::JsonMeasurement;
36///
37/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38/// let limits = JsonEncodeLimits::builder()
39/// .max_output_bytes(4_usize)
40/// .max_nodes(1_usize)
41/// .build();
42/// let mut session = JsonEncodeSession::from_limits(limits);
43/// let mut attempt = session.begin_value();
44/// attempt
45/// .try_consume_output_bytes(4)
46/// .expect("the output should fit");
47/// attempt
48/// .try_admit(JsonMeasurement::Null { depth: 1 })
49/// .expect("the value should fit");
50/// attempt.commit()?;
51/// assert_eq!(session.output_budget().expect("output budget").used(), 4);
52/// # Ok(()) }
53/// ```
54#[derive(Debug)]
55pub struct JsonEncodeSession<'a, R = JsonResource, Q = usize>
56where
57 Q: ResourceQuantity,
58{
59 /// Owned or borrowed budgets backing this encode operation.
60 storage: EncodeStorage<'a, R, Q>,
61}
62
63impl<'a, R, Q> JsonEncodeSession<'a, R, Q>
64where
65 R: Clone,
66 Q: ResourceQuantity,
67{
68 /// Creates a session borrowing only a caller-owned value budget.
69 ///
70 /// # Parameters
71 ///
72 /// * `value` - Caller-owned JSON value budget to update after a successful
73 /// encode attempt.
74 ///
75 /// # Returns
76 ///
77 /// Creates a session borrowing only a caller-owned value budget.
78 #[inline]
79 #[must_use]
80 pub fn borrowing_value(value: &'a mut JsonValueBudget<R, Q>) -> Self {
81 Self {
82 storage: EncodeStorage::Borrowed { output: None, value },
83 }
84 }
85
86 /// Creates a session borrowing caller-owned output and value budgets.
87 ///
88 /// # Parameters
89 ///
90 /// * `output` - Output supplied to this operation.
91 /// * `value` - Caller-owned JSON value budget to update after a successful
92 /// encode attempt.
93 ///
94 /// # Returns
95 ///
96 /// Creates a session borrowing caller-owned output and value budgets.
97 #[inline]
98 #[must_use]
99 pub fn borrowing_output(output: &'a mut ResourceBudget<R, Q>, value: &'a mut JsonValueBudget<R, Q>) -> Self {
100 Self {
101 storage: EncodeStorage::Borrowed {
102 output: Some(output),
103 value,
104 },
105 }
106 }
107
108 /// Starts accounting for one complete JSON value.
109 ///
110 /// The returned attempt charges accepted output immediately, but publishes
111 /// staged JSON value accounting only after a successful `commit`. A
112 /// value-admission failure poisons commit; dropping the attempt rolls back
113 /// only the staged value state.
114 ///
115 /// # Returns
116 ///
117 /// Starts accounting for one complete JSON value.
118 #[must_use]
119 pub fn begin_value(&mut self) -> JsonEncodeAttempt<'_, R, Q> {
120 let (output, value) = self.storage.split();
121 JsonEncodeAttempt::new(output, value.transaction())
122 }
123
124 /// Returns the output budget when configured.
125 ///
126 /// # Returns
127 ///
128 /// Returns the output budget when configured.
129 ///
130 /// `None` indicates that the corresponding limit or budget dimension is
131 /// unconfigured.
132 #[must_use]
133 #[inline(always)]
134 pub fn output_budget(&self) -> Option<&ResourceBudget<R, Q>> {
135 match &self.storage {
136 EncodeStorage::Owned { output, .. } => output.as_ref(),
137 EncodeStorage::Borrowed { output, .. } => output.as_deref(),
138 }
139 }
140
141 /// Returns the configured output-byte maximum.
142 ///
143 /// # Returns
144 ///
145 /// Returns the configured output-byte maximum.
146 ///
147 /// `None` indicates that the corresponding limit or budget dimension is
148 /// unconfigured.
149 #[must_use]
150 #[inline(always)]
151 pub fn max_output_bytes(&self) -> Option<Q> {
152 self.output_budget().map(ResourceBudget::limit)
153 }
154
155 /// Returns the value budget for read-only inspection.
156 ///
157 /// # Returns
158 ///
159 /// Returns the value budget for read-only inspection.
160 #[must_use]
161 #[inline(always)]
162 pub fn value_budget(&self) -> &JsonValueBudget<R, Q> {
163 match &self.storage {
164 EncodeStorage::Owned { value, .. } => value,
165 EncodeStorage::Borrowed { value, .. } => value,
166 }
167 }
168}
169
170impl<R, Q> JsonEncodeSession<'static, R, Q>
171where
172 R: Clone,
173 Q: ResourceQuantity,
174{
175 /// Creates a session that owns budgets initialized from immutable limits.
176 ///
177 /// # Parameters
178 ///
179 /// * `limits` - Immutable encoding limits used to initialize owned
180 /// accounting budgets.
181 ///
182 /// # Returns
183 ///
184 /// Creates a session that owns budgets initialized from immutable limits.
185 #[inline]
186 #[must_use]
187 pub fn from_limits(limits: JsonEncodeLimits<R, Q>) -> Self {
188 let output = limits.output_bytes_limit().cloned().map(ResourceBudget::from_limit);
189 let value = JsonValueBudget::new(limits.into_value_limits());
190 Self {
191 storage: EncodeStorage::Owned { output, value },
192 }
193 }
194}