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>(
124 &mut self,
125 value: &Value,
126 visitor: &mut V,
127 ) -> Result<(), JsonTreeProcessError<R, Q, V::Error>>
128 where
129 V: JsonTreeVisitor,
130 {
131 let mut pending = vec![ReadFrame::enter(
132 value,
133 JsonTreeContext {
134 depth: 1,
135 location: JsonTreeLocation::Root,
136 },
137 )];
138 while let Some(frame) = pending.last_mut() {
139 match &mut frame.state {
140 ReadFrameState::Enter => {
141 let value = frame.value;
142 let context = frame.context;
143 if self.enforce_limits {
144 if let JsonTreeLocation::ObjectValue { key } = context.location {
145 self.transaction
146 .try_admit(JsonMeasurement::Key { bytes: key.len() })?;
147 }
148 self.admit(value, context.depth)?;
149 }
150 visitor
151 .enter(value, context)
152 .map_err(JsonTreeProcessError::Visitor)?;
153 frame.state = ReadFrameState::Children(ChildCursor::new(value, context.depth));
154 }
155 ReadFrameState::Children(cursor) => {
156 if let Some((value, location, depth)) = cursor.next() {
157 pending.push(ReadFrame::enter(value, JsonTreeContext { depth, location }));
158 } else {
159 frame.state = ReadFrameState::Leave;
160 }
161 }
162 ReadFrameState::Leave => {
163 let frame = pending.pop().expect("read frame exists");
164 visitor
165 .leave(frame.value, frame.context)
166 .map_err(JsonTreeProcessError::Visitor)?;
167 }
168 }
169 }
170 Ok(())
171 }
172
173 /// Accounts every node and payload without invoking a domain visitor.
174 ///
175 /// The charges remain staged in the borrowed transaction. The caller
176 /// decides whether to commit it after any surrounding work succeeds.
177 ///
178 /// # Parameters
179 ///
180 /// * `value` - Root JSON value whose complete tree is admitted.
181 ///
182 /// # Returns
183 ///
184 /// `Ok(())` after every node and payload has been staged in the borrowed
185 /// transaction.
186 ///
187 /// # Errors
188 ///
189 /// Returns the first measured budget rejection encountered during the
190 /// traversal.
191 pub fn account(&mut self, value: &Value) -> Result<(), MeasuredBudgetError<R, Q>> {
192 if !self.enforce_limits {
193 return Ok(());
194 }
195 match self.process(value, &mut NoopVisitor) {
196 Ok(()) => Ok(()),
197 Err(JsonTreeProcessError::Budget(error)) => Err(error),
198 Err(JsonTreeProcessError::Visitor(error)) => match error {},
199 }
200 }
201
202 /// Admits one node before any visitor callback.
203 ///
204 /// # Parameters
205 ///
206 /// * `value` - JSON node whose resource use is measured.
207 /// * `depth` - Root-inclusive depth used by the measurement.
208 ///
209 /// # Returns
210 ///
211 /// Returns `Ok(())` after staging the node's measured resource use in the
212 /// borrowed transaction.
213 ///
214 /// # Errors
215 ///
216 /// Returns a measured budget error when the transaction cannot admit the
217 /// node within its configured resource limits.
218 fn admit(&mut self, value: &Value, depth: usize) -> Result<(), MeasuredBudgetError<R, Q>> {
219 self.transaction
220 .try_admit(json_value_measurement(value, depth))
221 }
222}