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#[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}