Skip to main content

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///
55/// An owned session owns its budgets, while resource identities may borrow
56/// caller data. The session remains bounded by the identity's lifetime.
57///
58/// ```compile_fail
59/// use qubit_budget::json::{JsonEncodeLimits, JsonEncodeSession};
60/// use qubit_budget::ResourceLimit;
61///
62/// fn invalid_session<'a>() -> JsonEncodeSession<'a, &'a str> {
63///     let label = String::from("local-output");
64///     let limits = JsonEncodeLimits::<&str, usize>::builder()
65///         .output_bytes_limit(ResourceLimit::new(label.as_str(), 2))
66///         .build();
67///     JsonEncodeSession::from_limits(limits)
68/// }
69/// ```
70#[derive(Debug)]
71pub struct JsonEncodeSession<'a, R = JsonResource, Q = usize>
72where
73    Q: ResourceQuantity,
74{
75    /// Owned or borrowed budgets backing this encode operation.
76    storage: EncodeStorage<'a, R, Q>,
77}
78
79impl<'a, R, Q> JsonEncodeSession<'a, R, Q>
80where
81    R: Clone,
82    Q: ResourceQuantity,
83{
84    /// Creates a session borrowing only a caller-owned value budget.
85    ///
86    /// # Parameters
87    ///
88    /// * `value` - Caller-owned JSON value budget to update after a successful
89    ///   encode attempt.
90    ///
91    /// # Returns
92    ///
93    /// Creates a session borrowing only a caller-owned value budget.
94    #[inline]
95    #[must_use]
96    pub fn borrowing_value(value: &'a mut JsonValueBudget<R, Q>) -> Self {
97        Self {
98            storage: EncodeStorage::Borrowed { output: None, value },
99        }
100    }
101
102    /// Creates a session borrowing caller-owned output and value budgets.
103    ///
104    /// # Parameters
105    ///
106    /// * `output` - Output supplied to this operation.
107    /// * `value` - Caller-owned JSON value budget to update after a successful
108    ///   encode attempt.
109    ///
110    /// # Returns
111    ///
112    /// Creates a session borrowing caller-owned output and value budgets.
113    #[inline]
114    #[must_use]
115    pub fn borrowing_output(output: &'a mut ResourceBudget<R, Q>, value: &'a mut JsonValueBudget<R, Q>) -> Self {
116        Self {
117            storage: EncodeStorage::Borrowed {
118                output: Some(output),
119                value,
120            },
121        }
122    }
123
124    /// Creates a session that owns budgets initialized from immutable limits.
125    ///
126    /// Resource identities in the limits may borrow caller data. The owned
127    /// budgets do not extend the lifetime of those identities.
128    ///
129    /// # Parameters
130    ///
131    /// * `limits` - Immutable limits used to initialize owned accounting
132    ///   budgets.
133    ///
134    /// # Returns
135    ///
136    /// A session owning its budgets and retaining the limits' resource
137    /// identities.
138    #[inline]
139    #[must_use]
140    pub fn from_limits(limits: JsonEncodeLimits<R, Q>) -> Self {
141        let output = limits.output_bytes_limit().cloned().map(ResourceBudget::from_limit);
142        let value = JsonValueBudget::new(limits.into_value_limits());
143        Self {
144            storage: EncodeStorage::Owned { output, value },
145        }
146    }
147
148    /// Starts accounting for one complete JSON value.
149    ///
150    /// The returned attempt charges accepted output immediately, but publishes
151    /// staged JSON value accounting only after a successful `commit`. A
152    /// value-admission failure poisons commit; dropping the attempt rolls back
153    /// only the staged value state.
154    ///
155    /// # Returns
156    ///
157    /// Starts accounting for one complete JSON value.
158    #[must_use]
159    pub fn begin_value(&mut self) -> JsonEncodeAttempt<'_, R, Q> {
160        let (output, value) = self.storage.split();
161        JsonEncodeAttempt::new(output, value.transaction())
162    }
163
164    /// Returns the output budget when configured.
165    ///
166    /// # Returns
167    ///
168    /// Returns the output budget when configured.
169    ///
170    /// `None` indicates that the corresponding limit or budget dimension is
171    /// unconfigured.
172    #[inline]
173    pub fn output_budget(&self) -> Option<&ResourceBudget<R, Q>> {
174        match &self.storage {
175            EncodeStorage::Owned { output, .. } => output.as_ref(),
176            EncodeStorage::Borrowed { output, .. } => output.as_deref(),
177        }
178    }
179
180    /// Returns the configured output-byte maximum.
181    ///
182    /// # Returns
183    ///
184    /// Returns the configured output-byte maximum.
185    ///
186    /// `None` indicates that the corresponding limit or budget dimension is
187    /// unconfigured.
188    #[inline]
189    pub fn max_output_bytes(&self) -> Option<Q> {
190        self.output_budget().map(ResourceBudget::limit)
191    }
192
193    /// Returns the value budget for read-only inspection.
194    ///
195    /// # Returns
196    ///
197    /// Returns the value budget for read-only inspection.
198    #[must_use]
199    #[inline]
200    pub fn value_budget(&self) -> &JsonValueBudget<R, Q> {
201        match &self.storage {
202            EncodeStorage::Owned { value, .. } => value,
203            EncodeStorage::Borrowed { value, .. } => value,
204        }
205    }
206}