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