qubit_json/value/traverse/json_tree_reader.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//! Implements non-recursive, read-only JSON tree processing.
9
10use qubit_budget::MeasuredBudgetError;
11use qubit_budget::ResourceQuantity;
12use qubit_budget::json::JsonMeasurement;
13use qubit_budget::json::JsonValueTransaction;
14use serde_json::Value;
15
16use super::JsonTreeContext;
17use super::JsonTreeLocation;
18use super::JsonTreeProcessError;
19use super::JsonTreeVisitor;
20use super::internal::ChildCursor;
21use super::internal::NoopVisitor;
22use super::internal::ReadFrame;
23use super::internal::ReadFrameState;
24use crate::value::internal::json_value_measurement;
25
26/// Processes JSON values while borrowing one staged JSON value transaction.
27///
28/// # Type Parameters
29///
30/// * `R` - Resource identity tracked by the borrowed transaction.
31/// * `Q` - Quantity representation used for resource accounting.
32///
33/// # Examples
34///
35/// ```
36/// use qubit_budget::json::{JsonResource, JsonValueBudget, JsonValueLimits};
37/// use qubit_json::value::traverse::{
38/// JsonTreeContext, JsonTreeReader, JsonTreeVisitor,
39/// };
40/// use serde_json::Value;
41///
42/// struct Visitor;
43/// impl JsonTreeVisitor for Visitor {
44/// type Error = std::convert::Infallible;
45///
46/// fn enter(
47/// &mut self,
48/// _: &Value,
49/// _: JsonTreeContext<'_>,
50/// ) -> Result<(), Self::Error> {
51/// Ok(())
52/// }
53/// }
54///
55/// let mut budget = JsonValueBudget::new(
56/// JsonValueLimits::<JsonResource, usize>::default(),
57/// );
58/// let mut transaction = budget.transaction();
59/// let mut reader = JsonTreeReader::new(&mut transaction);
60/// assert!(reader.process(&Value::Null, &mut Visitor).is_ok());
61/// ```
62pub struct JsonTreeReader<'transaction, 'budget, R, Q>
63where
64 Q: ResourceQuantity,
65{
66 /// Transaction receiving staged node and payload charges.
67 transaction: &'transaction mut JsonValueTransaction<'budget, R, Q>,
68 /// Whether any admission check can reject this traversal.
69 enforce_limits: bool,
70}
71
72impl<'transaction, 'budget, R, Q> JsonTreeReader<'transaction, 'budget, R, Q>
73where
74 R: Clone,
75 Q: ResourceQuantity,
76{
77 /// Creates a reader borrowing the supplied JSON value transaction.
78 ///
79 /// # Parameters
80 ///
81 /// * `transaction` - Transaction receiving node and payload charges.
82 ///
83 /// # Returns
84 ///
85 /// A reader borrowing `transaction` for its lifetime.
86 #[inline(always)]
87 #[must_use]
88 pub fn new(transaction: &'transaction mut JsonValueTransaction<'budget, R, Q>) -> Self {
89 let enforce_limits = transaction.has_limits();
90 Self {
91 transaction,
92 enforce_limits,
93 }
94 }
95
96 /// Processes every node in depth-first order without Rust recursion.
97 ///
98 /// # Type Parameters
99 ///
100 /// * `V` - Visitor receiving admitted-node callbacks.
101 ///
102 /// # Parameters
103 ///
104 /// * `value` - Root JSON value to process.
105 /// * `visitor` - Visitor invoked around each admitted node.
106 ///
107 /// # Returns
108 ///
109 /// `Ok(())` after the complete tree is processed.
110 ///
111 /// # Errors
112 ///
113 /// Returns [`JsonTreeProcessError::Budget`] when resource admission fails,
114 /// or [`JsonTreeProcessError::Visitor`] when the visitor rejects a node.
115 /// Either error stops traversal immediately: remaining callbacks are not
116 /// invoked, and a node may not receive a matching `leave` callback. Any
117 /// visitor state or external side effects are the caller's responsibility
118 /// and are not undone. This method neither commits nor rolls back the
119 /// borrowed transaction. A visitor error leaves prior charges staged in
120 /// that transaction; a budget error rejects the transaction so it cannot
121 /// be committed. Drop the transaction after an error and rebuild visitors
122 /// that require balanced callbacks or clean state.
123 pub fn process<V>(&mut self, value: &Value, visitor: &mut V) -> Result<(), JsonTreeProcessError<R, Q, V::Error>>
124 where
125 V: JsonTreeVisitor,
126 {
127 let mut pending = vec![ReadFrame::enter(
128 value,
129 JsonTreeContext {
130 depth: 1,
131 location: JsonTreeLocation::Root,
132 },
133 )];
134 while let Some(frame) = pending.last_mut() {
135 match &mut frame.state {
136 ReadFrameState::Enter => {
137 let value = frame.value;
138 let context = frame.context;
139 if self.enforce_limits {
140 if let JsonTreeLocation::ObjectValue { key } = context.location {
141 self.transaction.try_admit(JsonMeasurement::Key { bytes: key.len() })?;
142 }
143 self.admit(value, context.depth)?;
144 }
145 visitor.enter(value, context).map_err(JsonTreeProcessError::Visitor)?;
146 frame.state = ReadFrameState::Children(ChildCursor::new(value, context.depth));
147 }
148 ReadFrameState::Children(cursor) => {
149 if let Some((value, location, depth)) = cursor.next() {
150 pending.push(ReadFrame::enter(value, JsonTreeContext { depth, location }));
151 } else {
152 frame.state = ReadFrameState::Leave;
153 }
154 }
155 ReadFrameState::Leave => {
156 let frame = pending.pop().expect("read frame exists");
157 visitor
158 .leave(frame.value, frame.context)
159 .map_err(JsonTreeProcessError::Visitor)?;
160 }
161 }
162 }
163 Ok(())
164 }
165
166 /// Accounts every node and payload without invoking a domain visitor.
167 ///
168 /// The charges remain staged in the borrowed transaction. The caller
169 /// decides whether to commit it after any surrounding work succeeds.
170 ///
171 /// # Parameters
172 ///
173 /// * `value` - Root JSON value whose complete tree is admitted.
174 ///
175 /// # Returns
176 ///
177 /// `Ok(())` after every node and payload has been staged in the borrowed
178 /// transaction.
179 ///
180 /// # Errors
181 ///
182 /// Returns the first measured budget rejection encountered during the
183 /// traversal.
184 pub fn account(&mut self, value: &Value) -> Result<(), MeasuredBudgetError<R, Q>> {
185 if !self.enforce_limits {
186 return Ok(());
187 }
188 match self.process(value, &mut NoopVisitor) {
189 Ok(()) => Ok(()),
190 Err(JsonTreeProcessError::Budget(error)) => Err(error),
191 Err(JsonTreeProcessError::Visitor(error)) => match error {},
192 }
193 }
194
195 /// Admits one node before any visitor callback.
196 ///
197 /// # Parameters
198 ///
199 /// * `value` - JSON node whose resource use is measured.
200 /// * `depth` - Root-inclusive depth used by the measurement.
201 ///
202 /// # Returns
203 ///
204 /// Returns `Ok(())` after staging the node's measured resource use in the
205 /// borrowed transaction.
206 ///
207 /// # Errors
208 ///
209 /// Returns a measured budget error when the transaction cannot admit the
210 /// node within its configured resource limits.
211 fn admit(&mut self, value: &Value, depth: usize) -> Result<(), MeasuredBudgetError<R, Q>> {
212 self.transaction.try_admit(json_value_measurement(value, depth))
213 }
214}