Skip to main content

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}